hezided
hezided

Reputation: 65

Add an extra property to an element without inheritance

I am thinking about the best practice in OOP for the following problem:

We have a program that is working with an external API.

The API has an object of type Element which is basically a geometric element. Our application is a validation application that runs on a geometric model The application takes a collection of those elements and performs some geometric tests on them.

We wrap this API element with our own class called "ValidationElement" and save some additional information to this wrapper element that can not be obtained directly from the API Element but is required by our application.

So far so good, but now the application should expand and support other types of models (basically we can say that the app is running in a different environment). Specifically for this environment (and it does not apply to the previous cases), we want to save an additional parameter that obtaining it results in low performance.

What is the best practice option to implement it? On one hand, I would like to avoid adding extra parameters that are not relevant to a specific(the first) part of the program. And on the second hand, I am not sure that I want to use inheritance and split this object just for this small additional property.

public class ValidationElement
{
        public Element Element { get; set; }
        public XYZ Location {get; set;}//The extra property
}

The first and easy option is that the same class will have the additional property and calculation method:

public class ValidationElement
{
        public Element Element { get; set; }
        public XYZ Location {get; set;}//The extra property
        public string AdditionalProperty { get; set; }
        public void HardProcessingCalcOfAdditionalProperty()
        {
            //hard processing
            AdditionalProperty = result
        }
}

The second option that I mentioned is the inheritance

public class SecondTypeValidationElement : ValidationElement
{
        public string AdditionalProperty { get; set; }
        public void HardProcessingCalcOfAdditionalProperty()
        {
            //hard processing
            AdditionalProperty = result
        }
}

What do you think is the best practice for this? Is there any other way or design pattern that should help me achieve the goal?

Upvotes: 3

Views: 170

Answers (3)

StepUp
StepUp

Reputation: 38094

I would like to avoid adding extra parameters that are not relevant to a specific(the first) part of the program.

It looks like it is a sign that an inheritance shoulbe be avoided here. As there is a strong possibility that this behaviour is not applicable for other classes.

And this is the second reason to avoid of creation some abstraction:

Element which is basically a geometric element

Because:

So let's prefer composition over inheritance.

So, in my view, it would be really good if we move all heavy, tightly coupled logic of calculating of additional property to separate class:

public class ValidationElement
{
    public string Element { get; set; }

    public SomeExtra AdditionalProperty { get; set; }
}

public class SomeExtra
{
    public string Location { get; set; }//The extra property

    public string AdditionalProperty { get; set; }

    public void HardProcessingCalcOfAdditionalProperty()
    {
        //hard processing
        AdditionalProperty = string.Empty;
    }
}

Why have we created separate class SomeExtra and put logic here:

  • if we want to edit logic HardProcessingCalcOfAdditionalProperty, then we will edit just one class SomeExtra. By doing this we are satisfying Single Responsibility Principle of SOLID principles.
  • we can easily create some base abstract class for SomeExtra and then at runtime we can decide what concrete implementation should be injected. By doing this we are satisfying Open Closed Principle of SOLID principles.

UPDATE:

I really like this answer about whether inheritance or composition should be chosen:

My acid test for the above is:

  • Does TypeB want to expose the complete interface (all public methods no less) of TypeA such that TypeB can be used where TypeA is expected? Indicates Inheritance.

    • e.g. A Cessna biplane will expose the complete interface of an airplane, if not more. So that makes it fit to derive from Airplane.
  • Does TypeB want only some/part of the behavior exposed by TypeA? Indicates need for Composition.

    • e.g. A Bird may need only the fly behavior of an Airplane. In this case, it makes sense to extract it out as an interface / class / both and make it a member of both classes.

Update: Just came back to my answer and it seems now that it is incomplete without a specific mention of Barbara Liskov's Liskov Substitution Principle as a test for 'Should I be inheriting from this type?'

Upvotes: 1

T.S.
T.S.

Reputation: 19340

I am not going to repeat Open-close, DI, or other principals. It is already discussed. I would look at something like this, or even alternatively use Extensions to setup the value.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

// old updated
public class Element
{ 
    public Element(string msg) { Message = msg; } 
    public string Message;
}
public class XYZ { }
public class ABC { }
// new
public interface IDoesSomething 
{
    void SetResult();
}

// create 2 different wrappers
public class ValidationElementWrapper : IDoesSomething
{
    public ValidationElementWrapper(Element el) 
    {
        Element = el;
    }
    
    public Element Element { get; private set; }
    public XYZ Location {get; set;}
    
    public void SetResult()
    {
         Console.WriteLine("This is " + Element.Message);
         // Do nothing
    }
}

public class ValidationElementWrapper2 : IDoesSomething
{
    public ValidationElementWrapper2(Element el) 
    {
        Element = el;
    }
    
    public Element Element { get; private set; }
    public XYZ Location {get; set;}
    public string AdditionalProperty { get; set; }
    public void SetResult()
    {
        AdditionalProperty = "Set additional property on wrapper 2";
        Console.WriteLine("This is " + Element.Message + " and it has additional property - " + AdditionalProperty);
    }
}

// run your program
public class Program
{

    public static void Main()
    {
        
        var list = new List<IDoesSomething>();
        list.Add(new ValidationElementWrapper(new Element("Element 1")));
        list.Add(new ValidationElementWrapper2(new Element("Element 2")));
        
        list.ForEach(item => item.SetResult());
        
    }
        
}

Output
This is Element 1
This is Element 2 and it has additional property - Set additional property on wrapper 2

Alternatively, you can start with more basic class and then keep extending it

public class ValidationElementWrapper : IDoesSomething
{
    public ValidationElementWrapper(Element el) 
    {
        Element = el;
    }
    
    public Element Element { get; private set; }
    public XYZ Location {get; set;}
    
    public virtual void SetResult()  // <--- virtual
    {
         // Do nothing
         Console.WriteLine("This is " + Element.Message);
         
    }
}

public class ValidationElementWrapper2 : ValidationElementWrapper // <-- inheritnce
{
    public ValidationElementWrapper2(Element el) : base(el)
    {
        
    }
        
    public XYZ Location {get; set;}
    public string AdditionalProperty { get; set; }
    public override void SetResult() // <--- override
    {
        AdditionalProperty = "Set additional property on wrapper 2";
        Console.WriteLine("This is " + Element.Message + " and it has additional property - " + AdditionalProperty);
         
    }
}

Result will be the same

Upvotes: 1

Hamid Siaban
Hamid Siaban

Reputation: 345

OOP and SOLID best practice is to use abstractions (interfaces or abstract classes), wich is closer to your second approach.

Dependency Inversion Principle: The Dependency Inversion principle states that our classes should depend upon interfaces or abstract classes instead of concrete classes and functions.

Your first approach to edit the ValidationElement class is generally a bad idea, given that there are several environments for the project to be run onto. In addition, maintaining and developing the project on this approach is not scalable and will be a headache in the long run.

Open-Closed Principle: The Open-Closed Principle requires that classes should be open for extension and closed to modification.

I suggest below designing:

public interface IValidationElement
{
    Element Element { get; set; }
    XYZ Location {get; set;}//The extra property

    // declare other base properties and methods
}

public class ValidationElement: IValidationElement
{
    public Element Element { get; set; }
    public XYZ Location {get; set;}//The extra property

    // define other base properties and methods
}

public interface ISecondTypeValidationElement: IValidationElement
{
    string AdditionalProperty { get; set; }
    void HardProcessingCalcOfAdditionalProperty();
}

public class SecondTypeValidationElement: ISecondTypeValidationElement
{
    public string AdditionalProperty { get; set; }
    public void HardProcessingCalcOfAdditionalProperty()
    {
        //hard processing
        AdditionalProperty = result
    }
}

public interface IThirdEnvironmentValidationElement: IValidationElement
{
    string ThirdProperty { get; set; }
    void RequiredProcessing();
}

public class ThirdEnvironmentValidationElement: IThirdEnvironmentValidationElement
{
    public string ThirdProperty { get; set; }
    public void RequiredProcessing()
    {
        //related operations
    }
}

Upvotes: 1

Related Questions