When ThingC()
is a method:
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | using System; namespace ClassAClassBThingC { class Program { static void Main( string [] args) { ClassA classA = new ClassA(); classA.classB.DoSomething(); } } class ClassA { public ClassB classB = new ClassB(); } public class ClassB { public void DoSomething() { Console.WriteLine( "Hello World!" ); } } } |
using System; namespace ClassAClassBThingC { class Program { static void Main(string[] args) { ClassA classA = new ClassA(); classA.classB.DoSomething(); } } class ClassA { public ClassB classB = new ClassB(); } public class ClassB { public void DoSomething() { Console.WriteLine("Hello World!"); } } }
Notice line 11.
classA.classB.DoSomething();
classA.DoSomething();
Add method DoSomething()
to ClassA
and have it call classB.DoSomething()
.
In other words, make it the responsibility of ClassA
to give you access to DoSomething()
.
This gets rid of class Program
’s dependency on ClassB
.
The refactored code looks like:
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | using System; namespace ClassAClassBThingC { class Program { static void Main( string [] args) { ClassA classA = new ClassA(); classA.DoSomething(); } } class ClassA { public ClassB classB = new ClassB(); public void DoSomething() { classB.DoSomething(); } } public class ClassB { public void DoSomething() { Console.WriteLine( "Hello World!" ); } } } |
using System; namespace ClassAClassBThingC { class Program { static void Main(string[] args) { ClassA classA = new ClassA(); classA.DoSomething(); } } class ClassA { public ClassB classB = new ClassB(); public void DoSomething() { classB.DoSomething(); } } public class ClassB { public void DoSomething() { Console.WriteLine("Hello World!"); } } }
Notice how class Program
no longer has a reference to ClassB
.
Class Program
is no longer dependent on ClassB
.
Now notice that ClassA
creates an instance of ClassB
and holds on to it.
Another optional refactoring we can do is pass and instance of ClassB
into
ClassA
so ClassA
doesn't have to create it.
This is known as "Dependency Injection."
Whenever you see a class creating and storing a reference to another class, consider the possibility of passing the class in as a constructor parameter instead. This tends to make the class easier to unit test.
Let's do that here. Create ClassB
in the Main program
and pass it as a parameter to the ClassA
constructor:
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | using System; namespace ClassAClassBThingC { class Program { static void Main( string [] args) { ClassB classB = new ClassB(); ClassA classA = new ClassA(classB); classA.DoSomething(); } } class ClassA { public ClassB classB; public ClassA(ClassB classB) { this .classB = classB; } public void DoSomething() { classB.DoSomething(); } } public class ClassB { public void DoSomething() { Console.WriteLine( "Hello World!" ); } } } |
using System; namespace ClassAClassBThingC { class Program { static void Main(string[] args) { ClassB classB = new ClassB(); ClassA classA = new ClassA(classB); classA.DoSomething(); } } class ClassA { public ClassB classB; public ClassA(ClassB classB) { this.classB = classB; } public void DoSomething() { classB.DoSomething(); } } public class ClassB { public void DoSomething() { Console.WriteLine("Hello World!"); } } }
It may at first appear that all we did was move the reference to ClassA
back to class Program
, which may sound silly, because we just
refactored the code to get rid of the reference to ClassA
in class Program
, and here we are moving it right back again!
However, notice one important difference: class Program
isn't actually using ClassB
. It's just creating ClassB
and handing it to someone else.
The original code had the line classA.classB.DoSomething();
,
we were counting on ClassB
having a public DoSomething()
method.
This new refactored code makes no such call or assumption.
This refactoring may have made it easier to unit test; however, we still
have a reference to ClassB
in ClassA
.
To get rid of that, we can use the “Hollywood Principle”.
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | using System; namespace ClassAClassBThingC { class Program { static void Main( string [] args) { ClassA classA = new ClassA(); ClassB classB = new ClassB(); classA.DoSomething(); } } class ClassA { public static event EventHandler SomeEvent; public void DoSomething() { SomeEvent?.Invoke( this , new EventArgs()); } } public class ClassB { public ClassB() { ClassA.SomeEvent += this .DoSomething; } public void DoSomething( object sender, EventArgs e) { Console.WriteLine( "Hello World!" ); } } } |
using System; namespace ClassAClassBThingC { class Program { static void Main(string[] args) { ClassA classA = new ClassA(); ClassB classB = new ClassB(); classA.DoSomething(); } } class ClassA { public static event EventHandler SomeEvent; public void DoSomething() { SomeEvent?.Invoke(this, new EventArgs()); } } public class ClassB { public ClassB() { ClassA.SomeEvent += this.DoSomething; } public void DoSomething(object sender, EventArgs e) { Console.WriteLine("Hello World!"); } } }
ClassA
there is now no reference to ClassB
. ClassA
is no longer coupled to and dependent on ClassB
.ClassB
makes a reference to ClassA
.This is the classic “Publish / Subscribe” design pattern.
ClassA
publishes an event
which anyone interested may subscribe to.
Now ClassB
could clange, or could even disappear, and ClassA
won’t get upset about it.
Technically, if you run this program and trace the flow of execution, we still have ClassA
calling the ClassB
DoSomething()
method.
The call just looks different now. Instead of:
20 21 22 23 | public void DoSomething() { classB.DoSomething(); } |
public void DoSomething() { classB.DoSomething(); }we now have:
20 21 22 23 | public void DoSomething() { SomeEvent?.Invoke( this , new EventArgs()); } |
public void DoSomething() { SomeEvent?.Invoke(this, new EventArgs()); }
SomeEvent
is a list which ClassB
adds it's DoSomething()
method to.
When ClassA
invokes SomeEvent
it goes through
the methods on the SomeEvent
list and calls each one. Since
DoSomething()
is on the list, ClassA
calls it.