The best way to add behavior by type

I have a company

public class Company : Entity<Company>
{
     public CompanyIdentifier Id { get; private set; }
     public string Name { get; private set; }
     ..............
     ..........
}

      

The company can be an agent or a supplier or and or none . (There are more types.) Its behavior must be changed by type. The agent can get a commission, and the supplier can bill. What would be the best way to design object or objects or value objects? I have the ability to add multiple boolean types and check these values ​​inside methods,

 public class Company : Entity<Company>
    {
         public CompanyIdentifier Id { get; private set; }
         public string Name { get; private set; }
         public bool IsAgent { get; private set; }
         public bool IsSupplier { get; private set; }
         ..........

         public void Invoice()
        {
                if(!IsSupplier)
                {
                    throw exception.....;
                }
                //do something
        }

        public void GetCommission(int month)
        {
                if(!IsAgent)
                {
                    throw exception.....;
                }
                //do something
        }
         ..........
    }

      

I honestly don't like this. Is there any design pattern that can help overcome this scenario? What will you do and why design this scenario?

+3


source to share


3 answers


I explicitly implement the interfaces and then override the translation operator only when acting on that interface.

public class Company : ...., IAgentCompany, ISupplierCompany ... {

    public double IAgentCompany.GetCommission(int month) {
            /*do stuff */
    }

    public static explicit operator IAgentCompany(Company c)  {
        if(!c.IsAgent)
            throw new InvalidOperationException();
        return this;
    }
}

      

Explicit implementations of interfaces must be called through their interface, not a specific type:

// Will not compile
new Company().GetCommission(5);

// Will compile
((IAgentCompany)new Company()).GetCommission(5)

      

But now we have overloaded the explicit casting operator. So what does this mean? We cannot call GetCommission without casting it on the IAgentCompany, and we now have a protector to prevent this action for a company that is not flagged as an agent.

Good things about this approach:

1) You have interfaces that define aspects of different types of companies and what they can do. Interface segregation is a good thing and there are clear opportunities / responsibilities for each type of company.

2) You have eliminated checking for every feature you want to call that is not "global" for all companies. You do one check when you cast, and then while you are using it in a variable entered as an interface, you can happily interact with it without any additional check. This means fewer places to enter errors and less useless checks.



3) You leverage the power of languages ​​and use a type system to make your code more bulletproof.

4) You don't need to write tons of subclasses that implement various combinations of interfaces (maybe 2 ^ n subclasses!) With NotImplementedExceptions

or InvalidOperationException

everywhere else in your code.

5) You don't need to use an enumeration field or "Type", especially when you are asking to mix and match these sets of abilities (you need not just an enumeration, but an enumeration of the flag). Use the type system to represent different types and types of behavior, not an enumeration.

6) It's DRY.

Bad things about this approach:

1) Explicit interface implementations and overriding of explicit lith operators is not only bread and butter knowledge of C #, but can be confusing for those who come after you.

Edit:

Well I answered too quickly without testing the idea and it doesn't work for interfaces. However, see my other answer for a different idea.

+1


source


I would consider splitting the implementation for all of these types in different classes. You can start doing this by using an enumeration to represent the type of company.

public enum CompanyType
{
  Agent = 0,
  Supplier
}

public abstract class Company : Entity<Company>
{
   public CompanyIdentifier Id { get; private set; }
   public string Name { get; private set; }
   public CompanyType EntityType { get; private set; }

   public abstract void Invoice();
   public abstract void GetCommission(int month);
   ...

      

This way you get fewer public properties.

Then I would use the specialized classes for the provider and agent (and then both and neither). You can make abstract tags Company

and any specialized methods abstract.



This will allow you to separate the distinct behavior of each entity type. It will come in handy when you return to it for service. It also makes the code easier to read and understand.

public class SupplierCompany : Company
{
   public SupplierCompany()
   {
     EntityType = CompanyType.Supplier;
   }

   public override void Invoice()
   {...}
   public override void GetComission(int month)
   {...}
}

public class AgentCompany : Company
{
   public AgentCompany()
   {
     EntityType = EntityType.Agent;
   }

   public override void Invoice()
   {...}
   public override void GetComission(int month)
   {...}
}

      

With this, you can eliminate testing for different types in methods like Invoice

and GetComission

.

+1


source


As with most questions DDD

, it usually boils down to Bounded Contexts

. I would suggest that you are dealing with some specific bounded contexts here (this is most obvious from your statement "A company can be an agent or a supplier or both or not"). In at least one context, you need to treat all Company

entities the same, regardless of whether they are agents or providers. However, I think you need to think about whether your operations are applicable Invoice

or GetCommission

in this broader context? I would argue that they will be applied in more specialized contexts where the distinction between a Agent

and a Supplier

is much more important.

You might get in trouble because you are trying to create an all encompassing object Company

that is applicable in all contexts ... this is almost impossible to achieve without weird code constructs and fighting the type system (as suggested in your other answers).

Please read http://martinfowler.com/bliki/BoundedContext.html

As a rough idea of ​​what your contexts look like:

Broad "Company" Context
{
    Entity Company
    {
        ID : CompanyIdentifier
        Name : String
    }
}

Specialized "Procurement" Context
{
    Entity Supplier
    {
        ID : CompanyIdentifier
        Name : String
        Invoice()
    }
}

Specialized "Sales" Context
{
    Entity Agent
    {
        ID : CompanyIdentifier
        Name : String
        GetComission()
    }
}

      

Does it make sense to try and use the same object in the buying and selling contexts? After all, these contexts have very different requirements. One of the lessons of DDD is that we split the domain into these bounded contexts and don't try to create "God" objects that can do everything.

0


source







All Articles