Pragmatic Objects
  • Pragmatic Objects
  • Design
    • What's an Object ?
    • Thinking Object
    • Domain Modeling
    • Information Management
  • Practices
    • The Pragmatic Practices
    • No Getters & Setters
    • Inherit Wisely
    • Wrap Null
    • Model the Domain
    • Wrap Collections
    • Expose Snapshots
    • Abandon Composed Names
    • Don't Check Types
    • Minimize Knowledge
    • Immutability
    • Separate Commands & Queries
    • Abandon Statics
    • Declare Dependencies
    • Separate Instantiation from Usage
  • Patterns
    • Always Valid
    • Wrapper
    • Command
    • Procedural Object
    • Compute Object
    • Snapshot
    • Value Object
    • Observability
  • Examples
    • Celsiuses / Fahrenheits
Powered by GitBook
On this page
  1. Practices

Separate Instantiation from Usage

Keep the creation of objects separate from where they are used in order to benefit from po

PreviousDeclare DependenciesNextAlways Valid

Last updated 12 hours ago

Keep in mind that this practice applies to polymorphic objects, or in other words, objects that implement an interface.

It doesn't apply to other objects unless the creation process itself is complicated enough to consider it another responsibility.

Suppose we need to calculate taxes for two different types of entities in our system : people and companies.

The rules to calculate taxes are definitely not the same. In my country, France, people pay tax using a progressive scale. The more they earn, the higher the tax. A rate is applied by slices of revenues.

For companies, the tax is easier to calculate : 15% until a certain threshold and 25% above. For simplicity, we'll say the rate is fixed to 15%.

As you can see, simple leads us to note that :

  • All entities in the system pay taxes : that's our commonality

  • But the rule to pay tax differs from one entity to another : that's our variability

The use case of the system is to calculate taxes, so we'll have something like this at some point.

interface TaxCalculationPolicy {
    calculate(entity: Entity, income: Income): Tax;
}

class TaxCalculator {
    calculateTax(entity: Entity, income: Income) {
        // get the calculation policy from somewhere
        // then call it
        calculationPolicy.calculate(entity, income);
    }
}

The question is : where should that calculation policy come from ? We really have two solutions.

  • Either the TaxCalculator itself computes it

  • Or some other object computes it

The first solution, in a polymorphic context, must be avoided.

Why ? Because it breaks the Single Responsibility Principle and the Open/Closed Principle.

The SRP violation is prevalent : the TaxCalculator not only has the responsibility of creating the calculation policy, but it also have to use it.

SRP isn't simply about cohesion : it's about cohesion from the perspective of actors. An object should have only one vector of change, and clearly, TaxCalculator has two :

  • It can change because a new type of entity has been added to the system, or because the rules to determine which policy to create have evolved

  • Or it can change because the calculation itself involve additional steps (such as applying tax reductions

The OCP violation is directly linked to the first vector or change : adding a new type of entity into the system would involve modifying the TaxCalculator.

Imagine if there's more than two policies. For example, companies under a certain legal status have different rules, and people who are war veteran have a different tax policy. The code of the TaxCalculator will rapidly grow and is definitely not closed to modification. It will contain both the complexity of deciding which object to create, and how to use it.

The cleaner, more extensible solution is to have some other object create it : a factory, or more exactly, an Abstract Factory. Our factory is abstract because the product it creates, the TaxCalculationPolicy, is an abstract product.

We thus end up we two objects :

  • A factory, responsible for maintaining the rule of object creation

  • A client, responsible for managing the object

interface TaxCalculationPolicy {
    calculate(entity: Entity, income: Income): Tax;
}

class TaxCalculationPolicies {
    get(entity: Entity) : TaxCalculationPolicy {
        // return the correct policy
    }
}

class TaxCalculator {
    private readonly taxCalculationPolicies: TaxCalculationPolicies;
    
    calculateTax(entity: Entity, income: Income) {
        const policy = this.taxCalculationPolicies.get(entity);
        policy.calculate(entity, income);
        
        // Further steps
    }
}

The instantiation of the correct object has been isolated into TaxCalculationPolicies that serves as an Abstract Factory, while the usage of the object lies in the TaxCalculator.

The instantiation has been separated from usage.

Commonality Variability Analysis