Skip to main content
added 934 characters in body
Source Link
Ewan
  • 84.4k
  • 5
  • 91
  • 189

I should add a short note a couple of bad solutions to the dichotomy

The "Bounded Context", where instead of services we make a version of the Object for each operation.

 ObjectForA(dep1, dep2).CalcA()
 ObjectForB(dep3, dep4).CalcB()
 ObjectForC(dep5, dep6).CalcC()

This has a number of downsides. For one the naming gets ugly, two deciding which object gets which method can be hard, you end up with inheritance problems when you want objects to share common methods.

Another solution is to pass in different versions of a dependency depending on the operation you want

 var o = new obj(depA)
 var result = o.Calc() // returns A

 var o = new obj(depB)
 var result = o.Calc() // returns B

Very "Ask don't tell". However this falls into what I call the init() trap. Where an instance is created to work in a particular way, but can't be reused for a different purpose.

I should add a short note a couple of bad solutions to the dichotomy

The "Bounded Context", where instead of services we make a version of the Object for each operation.

 ObjectForA(dep1, dep2).CalcA()
 ObjectForB(dep3, dep4).CalcB()
 ObjectForC(dep5, dep6).CalcC()

This has a number of downsides. For one the naming gets ugly, two deciding which object gets which method can be hard, you end up with inheritance problems when you want objects to share common methods.

Another solution is to pass in different versions of a dependency depending on the operation you want

 var o = new obj(depA)
 var result = o.Calc() // returns A

 var o = new obj(depB)
 var result = o.Calc() // returns B

Very "Ask don't tell". However this falls into what I call the init() trap. Where an instance is created to work in a particular way, but can't be reused for a different purpose.

Source Link
Ewan
  • 84.4k
  • 5
  • 91
  • 189

I suggest you try writing the same section of code both ways.

At first it looks fine, just which class do you want your method on

program_ADM
{
    var service = new service(dep1,dep2)
    var o = new obj()
    var result = service.Calc(o)
}

vs

program_OOP
{
    var o = new obj(dep1,dep2)
    var result = o.Calc()
}

The difference starts to show itself when you have more than one operation

program_ADM
{
    var serviceA = new service(dep1,dep2)
    var serviceB = new service(dep3,dep4)
    var o = new obj()
    var resultA = serviceA.Calc(o)
    var resultB = serviceB.Calc(o)
}

vs

program_OOP
{
    var o = new obj(dep1,dep2, dep3, dep4)
    var resultA = o.CalcA()
    var resultB = o.CalcB()
}

You can see that if I have multiple unrelated operations to perform on the same data, OOP is going to get up with huge constructors with a million dependencies.

If I have a program which is only interested in resultA, I still need to create and inject the dependencies for resultB.

The OOP approach shines when you have multiple, related, state mutating, operations you want to conditionally perform on the same, persisted in memory object. ie a user interface

program_OOP
{
    var o = new obj(dep1,dep2)
    OnClickA += o.ChangeA()
    OnClickB += o.ChangeB()
    OnClickC += o.Print()

    while(running) { drawUI(); }
}

Now you can pass o around the app mutating it as required without having to dig out the service you want from some a global var etc

The ADM approach shines when you have a multiple unrelated calculations to perform on collections of the objects. ie microservices/web apis

program_ADM_MS1
{
    var serviceA = new service(dep1,dep2)
    var serviceB = new service(dep3,dep4)

    bind("/CalcA", o => return serviceA.Calc(o));
    bind("/CalcB", o => return serviceB.Calc(o));
}

program_ADM_MS2
{
    var serviceC = new service(dep5,dep6)
    var serviceD = new service(dep7,dep8)

    bind("/CalcC", o => return serviceC.Calc(o));
    bind("/CalcD", o => return serviceD.Calc(o));
}

program_ADM_MS3
{
    var serviceE = new service(dep9,dep0)
    foreach(var o in repo.AllObjs())
    {
       serviceE.DoThing()
    }
}