1

Note: See the update below, as I altered the original concept, based on the first answer+comment

I'm sorry if this is a trivial question or if it solely depends on the programmers preference. As I am not sure about real advantages or disadvantages concerning e. g. clarity or KISS, I ask this question.

In an abstract class concept where should I place the actual objects? (should I prefer BaseObject or DerivedPrivateObject)

public abstract class BaseClass<TBase>
{
    // Should we manage the generic object in the base class?
    protected TBase BaseObject = null;

    // Or should we access it via abstract property?
    protected abstract TBase DerivedObject { get; }

    // The derived implementation has to create the object
    protected abstract TBase CreateObject();

    public void DoSomethingGeneric()
    {
        // Base may directly use its base object
        BaseObject.GenericMethod();

        // Otherwise it has to access the derived property every time
        DerivedObject.GenericMethod();
    }
}

public class DerivedClass : BaseClass<TSpecialized> where TSpecialized: TBase
{
    // Should we manage the specialized object here in the derived class?
    private TSpecialized DerivedPrivateObject = null;

    protected TBase DerivedObject
    {
        get
        {
            return DerivedPrivateObject;
        }
    }

    protected TBase CreateObject()
    {
        DerivedPrivateObject = new DerivedObject();

        // We may additionally store this into the base object, but isn't this ugly?
        BaseObject = DerivedPrivateObject;

        return DerivedPrivateObject;
    }

    public void DoSomething()
    {
        // We can directly use the derived object type
        DerivedPrivateObject.SpecializedMethodNotInBaseType();

        // Otherwise we would have to convert
        (BaseObject as TSpecialized).SpecializedMethodNotInBaseType();
    }
}

Update (3) after the answer+comment of David Culp

Note to update 3: As I mentioned, I tried to simplify the classes to focus on the actual question. But as both commentators correctly stated, I failed with this. The simplification led to incorrect code and an incorrect concept. Because of this, I extended the below code structure to the real implementation and I hope this still is comprehensible. I am really sorry for any confusions that were provoked by the faulty simplification!

The references to LSP and DRY by David Culp allowed me to better understand my own question. This not only destroyed my doubts of having exagerated peanuts.

But also, this allowed me to come up with an acceptable solution:

public abstract class BaseClass<TBase> : ISomeInterface where TBase : class
{
    // We manage the generic object in the base class for allowing generic usage
    protected OtherBaseClass<TBase> BaseObject = null;

    // The derived implementation has to create the object
    protected abstract OtherBaseClass<TBase> CreateObject();

    public void Initialize()
    {
        // Let the derived class create the specialized object, but store it back into here, for generic usage
        BaseObject = CreateObject();
    }

    public void DoSomethingGeneric()
    {
        // Base may directly use the base object
        BaseObject.GenericMethod();
    }
}

public class DerivedClass : BaseClass<ISpecialized>
{
    private SpecializedType DerivedObjectAccessor
    {
        get
        {
            return BaseObject as SpecializedType;
        }
    }

    protected override OtherBaseClass<ISpecialized> CreateObject()
    {
        // Create the derived object, but let base class handle the assignment to BaseObject, for clarity reasons
        return new SpecializedType();
    }

    public void DoSomething()
    {
        // We may use specialized stuff via local accessor property
        DerivedObjectAccessor.SpecializedMethodNotInBaseType();
    }
}

Just for clarification:

public class SpecializedType : OtherBaseClass<ISpecialized>, ISpecialized
{
    // foreign implementation
}

Is this an acceptable solution concerning clarity and style?

Are there arguments against protected abstract void CreateObject(); not returning the object, letting the derived class assign it to BaseObject (error prone?) - I want to leave it open whether CreateObject() is called in a constructor already or via publicily available create method, but I won't omit the abstract method to let the derived logic know that is has to implement this.

(Update 2: as the above was horrible, I altered the create object handling)

The main point: Are there arguments against the principle of using a property (DerivedObjectAccessor) for local converted access to the generic base object?

Nicolas
  • 754
  • 8
  • 22
  • I'm guessing you've omitted the obvious constraint between `TBase` and `TSpecialized`; `where TSpecialized: TBase` (if not, then name them both `T`, otherwise its confusing). If you are going to leverage generic type variance, I suggest you start using interfaces unless you want invariant types, which is probably not the case. – InBetween Feb 20 '17 at 11:13
  • @InBetween you are correct, I missed the type dependency. My real case is more complex and while simplifying for this question, I wondered if the different types influence my question ... wasn't sure about this and postponed it for later evaluation - "later" didn't happen, I'm sorry – Nicolas Feb 20 '17 at 12:14
  • Now, after reflecting everything, of course the different types are important, cause this is what the question is about - having to convert `BaseObject` in the derived class all the time, or not (and storing the additional `DerivedPrivateObject`) – Nicolas Feb 20 '17 at 12:30
  • Regarding your edit: There is no reason to cast to `TSpecialized` -- inheriting from `BaseClass` passes it as the type parameter `TBase'. The cast will be a no-op. Also, as written, the constraint is a syntax error -- `TBase` is not defined in the declaration of `DerivedClass`. – David Culp Feb 20 '17 at 13:59
  • @DavidCulp you are absolutely right. Ok, so the simplifying does not work. I have extended the update to how the real scenario is. I am sorry for any confusions from my faulty simplification attempt! (the background is common management of different wcf clients, which is why `SpecializedType` is a little weird - from VS autogenerated code) – Nicolas Feb 20 '17 at 14:53
  • Is there a reason for `SpecializedType` to both implement the `ISpecialized` interface and inherit from a type that takes it as a type parameter? Just seems a bit odd. – David Culp Feb 20 '17 at 15:23
  • Honestly, I don't know if there is a reason. This is the Visual Studio autogenerated code on the client side when consuming the WSDL for my WCF service. It generates kilometers (or miles) of code, from which I need a fingertip only. But because of this mass (or mess), it is hard to conclude any relations. And it would not help either, as I have to use it as it is... :D – Nicolas Feb 20 '17 at 15:49

1 Answers1

2

It comes down to a trade-off between the Liskov Substitution Principle and DRY. LSP says to not force derived classes to implement anything they are not going to need, while DRY says to not code the same thing more than once.

So, best rule-of-thumb, place it as high (most generic) in the class hierarchy as it can go and still be used by all derived classes.

Update after edit

The primary confusion seems to be wanting to use the same type parameter (ISpecialized) in the class hierarchy and the type of a property while ensuring they are kept in sync. You may find something along these lines to be cleaner.

public class SpecializedType : OtherBaseClass<ISpecialized>, ISpecialized
{
    // foreign implementation
}

public abstract class BaseClass<TBase> : ISomeInterface
    where TBase : OtherBaseClass<ISpecialized>
{
    protected TBase BaseObject = null;

    protected abstract TBase CreateObject();

    public void Initialize()
    {
        BaseObject = CreateObject();
    }

    public void DoSomethingGeneric()
    {
        BaseObject.GenericMethod();
    }
}

public class DerivedClass : SpecializedType 
{
    protected DerivedClass  CreateObject()
    {
        return new DerivedClass();
    }

    public void DoSomething()
    {
        DerivedObjectAccessor.SpecializedMethodNotInBaseType();
    }
}

There are a few changes

  • not passing in the type parameter the two generic types are the synchronized with and just naming SpecializedType as the parameter in the declaration of DerivedClass while leaving the handling of that specialized type to 'DerivedClass'.
  • removed method that did nothing.
  • CreateObject becomes a basic Factory method for the class

I hope I have kept up with the edits, but let me know if I've gone off-track somewhere. (I am also going to request clarification in a comment).

Community
  • 1
  • 1
David Culp
  • 5,354
  • 3
  • 24
  • 32
  • Thanks for the references. In fact I am a DRY enthusiast, without knowing this named principle. This inner DRY addiction let me to express this question, because I clearly am not happy with having them both, `BaseObject` and `DerivedPrivateObject`. Not knowing the LSP as well, I intuitively prefered the highest possible placement of common objects - cause everything else would counteract the principle of abstraction. – Nicolas Feb 20 '17 at 12:36
  • The factory approach is a good idea. But the problem is, that different derivations have different `SpecializedType`s that use the same `TBase`. Therefore we cannot constraint it on `OtherBaseClass`. Next time it is `OtherBaseClass`. Or to go back to the real world (just in case that I fail in abstractions again): these are multiple wcf client autogenerated classes that derive from `System.ServiceModel.ClientBase, IAutogenerated` where `IAutogenerated` differs (and corresponds) to each client. – Nicolas Feb 20 '17 at 16:13