1

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.

1
  • So, this is one way to reuse code, but regarding your question about interfaces - you're not using polymorphism here, so switching to interfaces will not help, unless you change the design. Commented Jan 30, 2023 at 18:30

2 Answers 2

2

I think what you are looking for is a design which is more orthogonal - one where you can add new items with different properties, calculations and stability rules, without the need to extend ItemBase and CalculationResultBase in parallel. That is perfectly possible here, and likely more maintainable.

ItemBase seems to be the input to different calculations, and you want the type of item to control the calculation. That is fine, and I would consider to use inheritance only for your items, nothing else:

// does not really matter if it is an abstract class or an interface
public interface ItemBase
{
   double CalcResultingForce(double mass);
   bool IsSystemStable(double maxForce);
}


public class Item1 : ItemBase
{
   public int A { get; set; }
   public int B { get; set; }
   public override double CalcResultingForce(double mass)
   {
       return (A + B) / mass;
   }
   public override bool IsSystemStable(double force)
   {
       return force >= A;
   }
}

public class Item2 : ItemBase
{
   public int C { get; set; }
   public double MaxForce { get; set; }

   public override double CalcResultingForce(double mass)
   {
       return mass * C;
   }
   public override virtual bool IsSystemStable(double force)
   {
        return !(force  == 0 || MaxForce < value);
   }
}

This will allow one to implement CalculationResult and CalculationService in a more generic manner which does not require any changes or subclassing when a new type of item is added:

public class CalculationResult
{

    public bool SystemIsStable { get; set; }

    private double resultingForce;
    public virtual double ResultingForce
    {
        get => resultingForce;
        set
        {
            resultingForce = value;
            SystemIsStable = SomeItem.IsSystemStable(resultingForce);
        }
    }
    public ItemBase SomeItem { get; set; }
    public SystemInfo SystemInfo { get; set; }
    public CalculationResult(ItemBase someItem, SystemInfo systemInfo)
    {
        SomeItem = someItem;
        SystemInfo = systemInfo;
    }
}


public static CalculationResult Calculate(SystemInfo systemInfo, ItemBase item)
{
    CalculationResult output = new(item, systemInfo);
    output.ResultingForce = item.CalcResultingForce(systemInfo.Mass);

    return output;
}

Now, the whole core logic is in one place, and extensions can be done by adding further subclasses of ItemBase. It also removes the issue of the duplicate SomeItem, as mentioned by JonasH. As a bonus, this obeys also the OCP (in regards to potential new requirements for calculating the forces and stability criteria).

Note also I would not hesitate to change the design of your original CalculationResultBase class directly, as long you have the code under your own control. The OCP is not an end in itself, it makes most sense when creating components or libraries to be reused by other teams or organization, who want to be able to extend a library now, without asking the vendor to approve their new requirements in the next version which is released sixth months later in the future.

3
  • Thank you very much. I can and will rework CalculationResult ;) for now the SystemInfo seems a little unnecessary. I also think about adding a class SystemSetUp/SimulationSetUp that has the properties SystemInfo, Item and CalculationsResult. This way I can extract SystemInfo and Item out of CalculationResult. Commented Jan 31, 2023 at 9:02
  • @lorenzalbert: I would consider to make ItemBase more self-contained if possible, maybe by passing the mass into it at construction time. So it does not have to rely on a proper initialization sequence as shown in your Calculate method.
    – Doc Brown
    Commented Jan 31, 2023 at 9:43
  • Incorporating your answer arose new questions. If you have the time, I'd appreciate to hear your opinion about it. Commented Feb 1, 2023 at 7:17
2

I would argue that the idea of using multiple classes to represent some "thing" is fine, at least in some circumstances. This pattern can be used to model different aspects of the "thing". I have worked on systems where a new "thing" required new classes for: Creation, model, UI, persistence. While it requires a more classes, it helps at reducing the size of each class, and arguably makes the code easier to read.

I would probably prefer an interface instead of, or addition to, the base class(es). There is some patterns that are only possible with interfaces, like explicit interface implementations.

For example, your CalculationResultExtended has two different SomeItem properties that do not point to the same actual object, and this is a likely source for bugs. Using interface you could write something like

public interface ICalculationResult{
    ItemBase SomeItem { get; }
}
public class CalculationResultExtended : ICalculationResult{
    ItemBase ICalculationResult.SomeItem => this.SomeItem;
    public ItemExtended SomeItem {get;set;}
}

Now there is only one item, so there is no risk for mistakes. And the interface is read only, so it is impossible to set some other type of item.

1
  • +1 for the someitem thing. thats going to bite you
    – Ewan
    Commented Jan 30, 2023 at 14:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.