How do I implement the open-close principle?

108 Views Asked by At

My intention is to describe the architecture of a tennis court management software with a class diagram, keeping the open-closed principle.

Initially, the software should only provide the following functions:

  1. CourtOfAWeek(week:int):void. This function outputs all tennis courts in the console.
  2. CourtOfADay(day:int):void. This function outputs all tennis courts of a day in the console.

It should now be possible to add new functionality to the software at any time without having to change the existing software.

My solution concept is to apply the strategy pattern. Each functionality inherits from the abstract class AbstractFunction. I would also include all functions as a list in the Tennis_Center class. (see the class diagram and the code).

enter image description here

AbstractFunction:

 abstract class AbstractFunction{
    
 }

GetCourtByDayClass:

class GetCourtByDayClass:AbstractFunction{

    public void GetCourtByDay(int day){
        Console.WriteLine("Court 1, Court 2 and Court 3 are free.");
    }

}

GetCourtByWeekClass:

class GetCourtByWeekClass:AbstractFunction{

    public void GetCourtByWeek(int week){
        Console.WriteLine($"On Week {week} Court 5 and 6 are available");
    }

}

Tennis_Center:

List<AbstractFunction> functionList=new List<AbstractFunction>();

functionList.Add(new GetCourtByDayClass());
functionList.Add(new GetCourtByWeekClass());

functionList[1].GetCourtByWeek(1);

Console.ReadKey();

Now to my problem: I think I misunderstood or incorrectly implemented the strategy pattern. Now when I call the functions in the Tennis_Center class, I get the error message:

"AbstractFunction" does not contain a definition for "GetCourtByWeek", and no accessible GetCourtByWeek extension method could be found that accepts a first argument of type "AbstractFunction" (possibly a using directive or assembly reference is missing). [Tenniscenter]csharp(CS1061)

Can you give me some advice on which pattern is best for implementing the above situation.

3

There are 3 best solutions below

3
Yong Shun On
  1. Define an abstract method in AbstractFunction class.
abstract class AbstractFunction
{
    public abstract void GetCourt(int @value);
}
  1. For the derived classes, override the abstract method from base AbstractFunction.
class GetCourtByDayClass : AbstractFunction
{
    public override void GetCourt(int day)
    {
        Console.WriteLine("Court 1, Court 2 and Court 3 are free.");
    }
}

class GetCourtByWeekClass : AbstractFunction
{
    public override void GetCourt(int week)
    {
        Console.WriteLine($"On Week {week} Court 5 and 6 are available");
    }
}
  1. For the caller function:
functionList[1].GetCourt(1);

AbstractFunction func = new GetCourtByWeekClass();
func.GetCourt(1);

Demo @ .NET Fiddle

1
Adilet Soronov On

I think you are misusing the pattern.

The patterns are used to solve common problems. If you do not have a problem then do not complicate everything using patterns. In your case you don't need to use classes and maybe you need to learn about delegates Action<T> or Func<T>.

Now I'll show an example of when to use the Open-Close Principle:

public class Circle { }
 
public class Square { }
 
public static class Drawer {
   public static void DrawShapes(IEnumerable<object> shapes) {
      foreach (object shape in shapes) {
         if (shape is Circle) {
            DrawCircle(shape as Circle);
         } else if (shape is Square) {
            DrawSquare(shape as Square);
         }
      }
   }
   private static void DrawCircle(Circle circle) { /*Draw circle*/ }
 
   private static void DrawSquare(Square square) { /*Draw Square*/ }
}

As you can see DrawShapes is a function that uses shapes. Inside of it there is switch that checks the type and uses the corresponding functionality. The problem is that you can have hundreds of functions that use the same Shape and all of them use switch. Imagine you are adding a new Shape, then you need to change all hundreds of those functions adding new switch case to them and that's a huge problem, because you just wanted to add a new Shape but now you need to change 100 functions. It violates the Open-Close principle.

Let's look at how the principle solves this problem.

public interface IShape { void Draw(); }
 
public class Circle : IShape { public void Draw() { /*Draw circle*/ }}
 
public class Square : IShape { public void Draw() { /*Draw Square*/ } }
 
public static class Drawer {
   public static void DrawShapes(IEnumerable<IShape> shapes) {
      foreach (IShape shape in shapes) {
         shape.Draw();
      }
   }
}

The function DrawShapes or other 99 functions just use IShape interface (it could be just a base class with default implementation) and call it, meaning the function does not know which Shape was provided. Now if you want to add a new Shape then you only need to create a new class that implements the interface. The code is open for extending but closed for modifying (because you do not need to change the code in other places).

  1. Keep the code as simple and use the patterns when needed.
  2. My personal rule: use switch only for checking the type and only in one place. Otherwise it's a code smell.
0
StepUp On

yeah, I know that I am a little bit late to the party, but give me a try.

You are right that Strategy pattern can be used to select an algorithm at runtime. However, it is necessary to store these algorithms somewhere. I think, this is a place where Factory pattern can be used.

So let me show an example. I've little bit refactored your code as AbstractFunction is not good idea to call class.

So let's create an abstract class that will define common behaviour for all courts:

public abstract class Court
{ 
    public abstract void Get(int value);
}

And its concrete implementations CourtByDayClass and CourtByWeekClass :

public class CourtByDayClass : Court
{
    public override void Get(int day)
    {
        Console.WriteLine("Court 1, Court 2 and Court 3 are free.");
    }
}

public class CourtByWeekClass : Court
{
    public override void Get(int week)
    {
        Console.WriteLine($"On Week {week} Court 5 and 6 are available");
    }
}

Then we need a place to store these strategies and take it when it is necessary. This is a place where Factory pattern can be used:

public enum CourtType 
{
    Day, Week
}

public class CourtFactory
{
    private Dictionary<CourtType, Court> _courtByType = 
        new Dictionary<CourtType, Court>()
    {
        { CourtType.Day, new  CourtByDayClass() },
        { CourtType.Week, new CourtByWeekClass() },
    };

    public Court GetInstanceByType(CourtType courtType) => _courtByType[courtType];
}

And then you can call the above code like this:

TennisCenter tennisCenter = new TennisCenter();
CourtFactory courtFactory = new CourtFactory();
Court courtByDay = courtFactory.GetInstanceByType(CourtType.Day);
courtByDay.Get(1); // OUTPUT will be "Court 1, Court 2 and Court 3 are free."

So, here we've applied open closed principle. I mean you will add new functionality by adding new classes that will be derived from Court class.