I coudn't find a better phrasing for my question and hope it is not too confusing. my question mainly targets the open/closed principle and extensibility of my program. I tried to condense everything as good as possible.
Brief Overview
I have a program that acts like a test bench. For a given system, I have several Items I want to test whether they are suitable. I decided to keep the SystemInfo and the item separate. But I am not sure whether it might be better to add a class e.g. "TestSetUp".
Initial Concept
My program started with 4 classes. Given System = SystemInfo, Items to test = ItemBase,
public class ItemBase
{
public int A { get; set; }
public int B { get; set; }
}
Result = CalculationResultBase
public class CalculationResultBase
{
public bool SystemIsStable { get; protected set; }
protected double resultingForce;
public virtual double ResultingForce
{
get => resultingForce;
set
{
resultingForce = value;
if (value < SomeItem.A)
{
SystemIsStable = false;
}
else
{
SystemIsStable = true;
}
}
}
public ItemBase SomeItem { get; set; }
public SystemInfo SystemInfo { get; set; }
public CalculationResultBase(ItemBase someItem, SystemInfo systemInfo)
{
SomeItem = someItem;
SystemInfo = systemInfo;
}
}
test/simulation = CalculationService
public static class CalculationService
{
public static CalculationResultBase Calculate(SystemInfo systemInfo, ItemBase item)
{
CalculationResultBase output = new(item, systemInfo);
output.ResultingForce = (item.A + item.B) / systemInfo.Mass;
return output;
}
}
Extending the Concept
But there also exist items with more or different information/Properties. The Calculation might differ and also the result.
So I added two classes that extend ItemBase and CalculationResultBase
ItemExtended
public class ItemExtended : ItemBase
{
public int C { get; set; }
public double MaxForce { get; set; }
}
and CalculationResultExtended
public class CalculationResultExtended : CalculationResultBase
{
public new ItemExtended SomeItem { get; set; }
public override double ResultingForce
{
get => resultingForce;
set
{
resultingForce = value;
if (value == 0 || SomeItem.MaxForce < value)
{
SystemIsStable = false;
}
else
{
SystemIsStable = true;
}
}
}
public CalculationResultExtended(ItemExtended someItem, SystemInfo systemInfo) : base(someItem, systemInfo)
{
SomeItem = someItem;
}
}
and added a new calculation to the CalculationService
public static CalculationResultExtended Calculate(SystemInfo systemInfo, ItemExtended itemExtended)
{
CalculationResultExtended output = new(itemExtended, systemInfo);
output.ResultingForce = systemInfo.Mass * itemExtended.C;
return output;
}
Question
At the moment I use inheritance to extend my program. And in the future there might be the need to implement more items (with slightly different behaivour or calculation). With this approach I need to add two new classes (one for the Item and one for the CalculationResult) and add a new function in the CalculationService.
Does this approach (adding 2 classes and editign one class every time a new Type of items is added) have major flaws especially with regards to the open closed principle? Is using interfaces better?
If you have any other concerns please do not hesitate to point them out.
Edit 2023/02/01 based on Doc Brown's answer
Imagine using ItemExtended will cause heat dissipation. I'd add an interface IDissipatesHeat:
public interface IDissipatesHeat
{
double HeatDissipation(SystemInfo systemInfo);
}
which is implemented by ItemExtended
public class ItemExtended: ItemBase, IDissipatesHeat
{
...
public double HeatDissipation(SystemInfo systemInfo)
{
return systemInfo.Mass * systemInfo.Velocity * systemInfo.Velocity / 2;
}
}
CalculationResult is updated with the amount of HeadtDissipation
public class CalculationResult
{
public double HeatDissipation { get; set; } = 0;
...
}
The calculate Method will check whether IDissipatesHeat is implemented and update the CalculationResult.
CalculationResult Calculate(ItemBase item, SystemInfo systemInfo)
{
CalculationResult output = new(...);
if (item is IDissipatesHeat temp)
output.HeatDissipation = temp.HeatDissipation(systemInfo);
...
return output;
}
or I overload the Calculate Method
CalculationResult Calculate(ItemBase item, SystemInfo systemInfo)
{
CalculationResult output = new(...);
// some calculation
return output;
}
CalculationResult Calculate(ItemExtended item, SystemInfo systemInfo)
{
CalculationResult output = Calculate(item as ItemBase, systemInfo);
output.HeatDissipation = item.HeatDissipation(systemInfo);
return output;
}
Follow up question
Overloading makes the system closed but adding code to the existing Calculate method gives me more control. I am just brainstorming "what if the HeatDissipation influences the base calculation?". Maybe I am just too worried ...
Is it okay to add a property HeatDissipation
to CalculationResult, which will always be 0 if ItemBase is used? Otherwise I'd need an interface like IHasHeatDissipationInformation
.