Home > Software design >  How do I implement the open-close principle?
How do I implement the open-close principle?

Time:08-16

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.

CodePudding user response:

  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

CodePudding user response:

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.
  • Related