6
\$\begingroup\$

I'm currently working on a project which involves EntityFramework with a (very) rich model (lots of inheritance). I'm currently facing a problem, quite interesting in my opinion.

My model is described in the Domain namespace. It involves 2 classes that inherit from 1 abstract class.

What I want to do is to be able to perform some specific actions on instances of these classes without knowing their concrete type. I could of course use abstract or virtual methods directly in the model.

But I have many reasons for not doing that:

  • I want to keep my model classes as straight POCOs
  • In real life, those modifying methods will have dependencies on other objects (that could be database or network retrieving of information), and I want to keep my model without any dependency

After reading on that problem it appeared to me that a solution to this problem is to use the Visitor pattern. But this implies to modify my model, so I thought about finding another solution, and I came to this one.

I've 2 problems with that solution:

  • The use of the dynamic keyword in the static resolution mechanism
  • The abstract getter in the public abstract class PersonModifier

Is this a known pattern? If not, is it clean? If not, is there an appropriate pattern to perform such an operation?

namespace Domain
{
    public abstract class Person
    {
        public int Age { get; set; }
        public string Name { get; set; }
    }

    public class Child : Person
    {
        public decimal PocketMoney { get; set; }
    }

    public class Adult : Person
    {
        public string FavoriteBook { get; set; }
    }
}

namespace Modifiers
{
    public static class Resolver
    {
        private static PersonModifier ConcreteResolve(Domain.Child person)
        {
            return new ChildModifier(person);
        }

        private static PersonModifier ConcreteResolve(Domain.Adult person)
        {
            return new AdultModifier(person);
        }

        private static PersonModifier ConcreteResolve(Domain.Person person)
        {
            throw new System.Exception("Impossible, can't find an appropriate modifier for type " + person.GetType());
        }

        public static PersonModifier Resolve(this Domain.Person person)
        {
            return ConcreteResolve((dynamic)person);
        }
    }

    public abstract class PersonModifier
    {
        protected abstract Domain.Person Person { get; }

        public virtual void PerformSpecificActions()
        {
            this.Person.Age += 1;
            this.Person.Name = "Hi " + this.Person.Name;
        }
    }

    public class ChildModifier : PersonModifier
    {
        protected Domain.Child _child;

        public ChildModifier(Domain.Child child)
        {
            this._child = child;
        }

        protected override Domain.Person Person
        {
            get { return this._child; }
        }

        public override void PerformSpecificActions()
        {
            base.PerformSpecificActions();
            this._child.PocketMoney *= 10;
        }
    }

    public class AdultModifier : PersonModifier
    {
        protected Domain.Adult _adult;

        public AdultModifier(Domain.Adult adult)
        {
            this._adult = adult;
        }

        protected override Domain.Person Person
        {
            get { return this._adult; }
        }

        public override void PerformSpecificActions()
        {
            base.PerformSpecificActions();
            this._adult.FavoriteBook = "Utopia";
        }
    }
}

namespace ConsoleApplication7
{
    using Modifiers;

    public class Program
    {
        public static void Main(string[] args)
        {
            var child = new Domain.Child
            {
                Age = 12,
                Name = "Mark",
                PocketMoney = 12.5m
            };

            Domain.Person childAsPerson = child;
            System.Console.WriteLine("Name:" + child.Name + ", Age:" + child.Age + ", PocketMoney:" + child.PocketMoney);
            childAsPerson.Resolve().PerformSpecificActions();
            System.Console.WriteLine("Name:" + child.Name + ", Age:" + child.Age + ", PocketMoney:" + child.PocketMoney);

            var adult = new Domain.Adult
            {
                Age = 42,
                Name = "Lola",
                FavoriteBook = "Dorian Gray"
            };

            Domain.Person adultAsPerson = adult;
            System.Console.WriteLine("Name:" + adult.Name + ", Age:" + adult.Age + ", FavoriteBook:" + adult.FavoriteBook);
            adultAsPerson.Resolve().PerformSpecificActions();
            System.Console.WriteLine("Name:" + adult.Name + ", Age:" + adult.Age + ", FavoriteBook:" + adult.FavoriteBook);
        }
    }
}
\$\endgroup\$
2
  • \$\begingroup\$ This feels like you need a Domain Layer class that encapsulates the Person/Child/Adult object and the other non database dependencies. This object would then expose modifier methods that do the actual work. These modifier methods would ferry data from the non database entities to your database entities via additional method calls. Could you post a more realistic example of the "specific actions?" \$\endgroup\$ Feb 27, 2016 at 0:05
  • 2
    \$\begingroup\$ Once again, there is no Console.WriteLine() in my production code. This is exactly why one of the CodeReview rules states, that you have to post the real code you own and maintain when asking a question. It is impossible to tell, which design is better in your case because your hypothetical code does not behave the same way your real code does. IMHO, if you want to recive a decent answer, you should either come up with an example, which accurately demonstrates both the problem and the usecase, or just post your real code. For now, I voted to close your question. \$\endgroup\$
    – Nikita B
    Feb 29, 2016 at 11:06

2 Answers 2

5
\$\begingroup\$

I want to keep my model without any depdency

When one class uses another, that's dependency. OO programming is all about dependencies - we do not (should not) make single monolithic, do-all classes. However Decoupling direct dependencies is what all the 'oopla is about.


System.Console.WriteLine("Name:" + child.Name + ", Age:" + child.Age + ", PocketMoney:" + child.PocketMoney);

Should be more like System.Console.WriteLine(child), where of course ToString() is overridden. Single Responsibility Principle.


        var adult = new Domain.Adult
        {
            Age = 42,
            Name = "Lola",
            FavoriteBook = "Dorian Gray"
        };

Instead use Constructor parameters, providing the opportunity for validating arguments.


POCO is loco

What's the point of any class when we can shovel scheiss anywhere at anytime using any value? If we cannot have some modicum of "state management" why have any particular class at all?

As your project grows sooner or later it will dawn on you that all the Person and derivative class functionality is everywhere in the client code and nowhere in the class itself! Woe betide the maintenance programmer.


Command Pattern Trying To get out?

That's what it feels like to me. C.P. purpose is to decouple the requestor and performer of a request.


MVC is about decoupling dependencies

At this higher level perspective we can swap out model or view or controller components. Perhaps instead of (what I see as) odd class metamorphosis, design a coherent (business) model and have customized controllers as needed.


Closures and delegates

I've used this idea, where setting the delegate via constructor overloads. Each delegate iterated a different data structure but otherwise performed the same function.

public class Child : Person 
{
    protected Func<double> Allowance;
    public double PocketMoney {get; protected set;}

    public Child (int goodBoyBonus) {
        goodBoyBonus = goodBoyBonus <= 0 ? 1 : goodBoyBonus;

         Allowance = delegate { this.PocketMoney *= goodBoyBonus }; 
    }

    public Child (Func<double> PMdelegate) {
        if (PMdelegate == null) throw new ArguementNullException();
        Allowance = PMdelegate;
    }

    public PerformSpecificActions() { Allowance(); }
}

And maybe there are several pre-made "delegations" and via a constructor or a factory method the desired delegate is specified. This technique can be applied as the command pattern without command objects per se.

\$\endgroup\$
8
  • \$\begingroup\$ I hope you do usually write curly brackets on new lines (at least when you’re writing C#) ^^ …I don’t agree with your point of view. POCOs are normally passed between all kind of application layers. They should only contain data (serializable) and no logic at all. The logic may be different for each layer. \$\endgroup\$ Feb 26, 2016 at 7:46
  • \$\begingroup\$ I'm not OK with that solution. 1 - The Console.WriteLine() and object initializators used in Main() are only here for demonstration purpose. As I said, I use EntityFramework in real life. 2 - When using EntityFramework, I prefer to separate the business logic from the model (hence POCOs). In my case, the business logic is located in service classes. \$\endgroup\$
    – Rémi A
    Feb 26, 2016 at 8:42
  • 2
    \$\begingroup\$ @RémiA Having all your business logic in service classes is the definition of an anaemic model: martinfowler.com/bliki/AnemicDomainModel.html . Maybe that's what you want, but in your question you say you have a rich domain \$\endgroup\$ Feb 26, 2016 at 13:35
  • \$\begingroup\$ We've used the POCO paradigm too. Our POCO'd classes were designed as a composition element in a coherent business class. Otherwise, often I've seen where POCO expediency goes; the WriteLine code is a perfect example how it gets started. Foretold is forewarned. \$\endgroup\$
    – radarbob
    Feb 26, 2016 at 13:39
  • \$\begingroup\$ The code given here is a example implementation of my problem that serves as a POC before implementing it for real. \$\endgroup\$
    – Rémi A
    Feb 26, 2016 at 14:46
2
\$\begingroup\$

You solution is good.
I prefer a switch for readability but I think this is a matter of taste.
From a testability view it would be better to replace the extension method with a “Service” for this, which gets injected using dependency injection:

public interface IModifierResolver
{
    PersonModifier Resolve( Person person );
}

public class ModifierResolver : IModifierResolver
{
    #region Implementation of IModifierResolver

    public PersonModifier Resolve( Person person )
    {
        switch ( person.GetType()
                       .Name )
        {
            case nameof( Child ):
                return new ChildModifier( person as Child );
            case nameof( Adult ):
                return new AdultModifier( person as Adult );
            default:
                throw new Exception( "Impossible, can't find an appropriate modifier for type " + person.GetType() );
        }
    }

    #endregion
}
\$\endgroup\$
1
  • \$\begingroup\$ Thanks for your answer. My opinion is : 1 - I don't like to use the "switch" keyword in that case, because implies to modify the code of an existing method instead of adding a method when a new sub-type comes in the model. (and switch is visually ugly when using polymorphism. Although I agree that "dynamic" is a hidden switch...) 2 - Your reflexion on dependency injection is perfectly right! I also thought about it. But I realized that "dynamic" just does the work here. Maybe I'll rewrite the resolver to use DI. \$\endgroup\$
    – Rémi A
    Feb 25, 2016 at 22:59

Your Answer

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

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