5

Lets assume I have following base class:

public class Base 
{
    public int Id {get; set;}
    public string SomeText {get; set;}
    public string SomeOtherText {get; set;}

    public static Base BuildFromOtherAssembly(ClassFromAssemblyA source)
    {
        Id = source.Id;
        SomeText = source.SomeText;
        SomeOtherText = source.SomeOtherText
    }
}

public class Derived : Base 
{
    public DataFromAssemblyB DataFromAssemblyB {get; set;}

    public Derived(DataFromAssemblyB dataFromAssemblyB)
    {
        DataFromAssemblyB = dataFromAssemblyB;
    }
}

The base class gets built and returned from a Assembly that references AssemblyA, to AssemblyB, which does not know anything about AssemblyA.

AssemblyB now wants to add it's own data to the Base class, and wants to make sure the return type indicates that the additional data is present.

Basically, I want to make sure on a compiler level that DataFromAssemblyB is present.

I know that composition would work well in this scenario, but the problem is that logically DataFromAssemblyB belongs to Base.

To give a more real world example, imagine this scenario:

public class ShoppingCart 
{
    public int Id {get; set;}
    public string OwnerName {get; set;}
    public decimal Value {get; set;}

    public static Base BuildFromDataModel(ShoppingCartDatabaseModel source)
    {
        Id = source.Id;
        OwnerName = source.OwnerName;
        Value = source.Value
    }
}

public class ShoppingCartWithContents : ShoppingCart 
{
    public ShoppingCartContent Content {get; set;}

    public ShoppingCartWithContents (ShoppingCartContent content)
    {
        Content = content;
    }
}

I hope this explains it. The reason I have this problem is because ShoppingCart comes from the database layer, while ShoppingCartContent comes from the caching layer, and a intermediary layer is joining the data together.

I want strong typing so it's always clear to the developer whether they are dealing with a ShoppingCart that has contents loaded, or a ShoppingCart that does not.

I want to avoid mapping all properties around by hand, as that could introduce bugs should a developer decide to add a property to ShoppingCart, but forget to map that property in ShoppingCartWithContents.

Please let me know what the best practice in such a situation is.

Update:

I created a few different variations I came up with on Github, would love to get some feedback and thoughts.

Composition? Inheritance? Gist

The premise is the same - we create a shopping cart object from a mock database model, and then try to add content data to it.

To test various capabilities, I thought it would be nice if the ShoppingCart has a .AddContents(content) method that allows to cleanly create a ShoppingCartWithContents.

As a additional functionality test as a stand in for Business Logic, I added a GetFriendlyString method which should still be accessible once items have been added.
In some samples I overrode GetFriendlyString to have a extra "With Items" to show that the base functionality can be extended.

I tried to summarize my feelings on the various approaches at the top.

Update 2:

I wanted to give a bit more information on the circumstances, as that may influence what the best way is.

This is a web api project, with EF as a ORM. My business objects like ShoppingCart are hand-mapped from EF's Db Models to only pick the properties relevant for the business task. Thus, my business logic is not constrained by the ORM or Data Layer ( in fact, the assembly that contains ShoppingCart does not even reference EF ).

Updates and creates are handled through their own business models, for example if a shopping cart needs to be updated, there would be a ShoppingCartUpdate class that has validation rules and only knows how to map it's changes to the database model.

Some data is cached in Redis, and some situations call for creating business objects from both EF and Redis. In the shopping cart example for instance, some business logic may only need to know if a user has a shopping cart or not, but other business logic that has to sum up the price relies on that extra information being available.

I want to avoid using bools like HasDataLoaded, and I also want to avoid nullable types as that inevitably leads to repeating nullchecks and if statements all over the codebase.

Worse yet, if a dev forgets a if / null check it could potentially try to ship out a empty order or something stupid like that.

That's why I thought it would be a better approach to enforce the presence or absence of data using unique classes - a checkout method would only take a ShoppingCartWithContents parameter, thus ensuring that the content has been loaded.

I hope that makes sense.

  • You dont state the language. But if the base class references assemblyA the dervided class will usualy have to too. At the very least you risk a runtime error – Ewan Jan 30 '16 at 19:02
3

Looking at your gists, it seems like regular old inheritance will do the trick, especially if you want to be able to process an instance of ShoppingCartWithContents in a method that only cares about the properties of ShoppingCart.

  • I ended up following this advice. I switched it to using composition and use automapper to map it to view models. It works well enough, but is kind of inconvenient from a programming standpoint, as I feel it introduces a lot of unnecessary class hopping. ShoppingCartComposite.ShoppingCart.Id. – Bio2hazard Feb 16 '16 at 19:36
  • Also, could you go a bit into detail why inheritance should be avoided in a situation like this / how it will bring pain ? – Bio2hazard Feb 16 '16 at 21:14
  • Thank you so much for the update, it's very insightful. I wanted to clarify that I am not only trying to use ShoppingCartWithContents in the view layer, but in the business process layer as well. As an example, I may want to add a "VerifyContentsInStock" functionality to ShoppingCartWithContents. Then, in my Checkout method, I set the parameter to ShoppingCartWithContents and can make sure no one tries to check out a cart without having contents loaded. I updated the question with some more code samples and information about the circumstances. Would love to receive more wisdom from you! – Bio2hazard Feb 18 '16 at 1:19
  • OK, given the extra info, it seems I have made some incorrect assumptions. Looking at your gists, it seems like regular old inheritance will do the trick, especially if you want to be able to process an instance of ShoppingCartWithContents in a method that only cares about the properties of ShoppingCart. – Trey Mack Feb 18 '16 at 2:29
1

Please let me know what the best practice in such a situation is.

I don't know about "the best practice", but it sure sounds like the state design pattern. Quote from the link:

Intent

  • Allow an object to alter its behavior when its internal state changes. The object will appear to change its class.

  • An object-oriented state machine

  • wrapper + polymorphic wrappee + collaboration


I observe that state pattern is a polymorphic behavior change in response to state change; it is not merely composition vs. inheritance debate resolution.

However the pattern (all patterns) is guidance not gospel so I would not say variation deviates from "the best practice".


Basically, I want to make sure on a compiler level that DataFromAssemblyB is present.

The base class is abstract. I think the pattern is suggesting that the ShoppingCart and ShoppingCartWithContent both inherit from this base. Between constructor parameters, declared abstract and virtual members (C# lingo) sounds like your concern is covered.


I want to avoid mapping all properties around by hand, as that could introduce bugs should a developer decide to add a property to ShoppingCart, but forget to map that property in ShoppingCartWithContents.

Perhaps the mapping - AutoMapper solution by @tmack for example - would be in the ShoppingCartWithContents with default code in the base for triggering it in a constructor. This kinda sounds like the visitor pattern, conceptually at least (I don't know how AutoMapper does it's thing).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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