Open-closed principle VS DRY - oop

Let's say we had only one payment method in application. We created one interface and one implementation:
interface Payment {
//
}
class InitialPayment implements Payment {
//
}
Later on the product owner asked to add another payment method. We didn't brake the open-closed principle and created second implementation:
class AnotherPayment implements Payment {
//
}
Everything looks good and let's say everything works fine. But what if these 2 classes have 80% common logic and code? Did we brake don't repeat yourself principle?
Or did I miss something?

Related

Confusion about open/closed principal

Open/closed principle states that classes are closed for modifications but open for extensions. Lets say we want to design a payment system where payment can be processed by multiple processors like following:
class Payment {
void pay(paymentMethod) {
switch (paymentMethod) {
case 'PayPal':
break;
case 'Swift':
break;
default:
break;
}
}
}
class PayPal {
void pay() {
//implement payment1
}
}
class Swift {
void pay() {
//implement payment2
}
}
Let's say we implement both payment systems the first time. Now if for some reason any payment system implementation process is changed, should not we have to modify the relevant class? For example, if we implement PayPal and after 2-3 years PayPal's working process is changed, does not modifying the PayPal class break the open/closed principle? If it does what's the solution?
Having that switch statement in your Payment classes breaks the open/closed principle because it makes the abstract idea of a Payment tightly coupled to the concrete implementations PayPal and Swift. In order to add a remove a supported payment type, you would have to edit the Payment.pay() method.
A better design uses an interface to describe what a payment provider should look like. In this case, that it must have a void pay() method.
Instead of taking a paymentMethod argument as a string, Payment.pay() should accept an instance of a class which implements the payment provider interface. It can call paymentMethod.pay() to execute the correct function. (Depending on your actual setup, it's probably better to pass this argument to the constructor than to a method).
This way it becomes trivially easy to add or remove payment providers because the Payment class does not need any knowledge whatsoever about which provider classes exist.
interface PaymentProvider {
void pay();
}
class Payment {
void pay(paymentMethod: PaymentProvider) {
paymentMethod.pay();
}
class PayPal implements PaymentProvider {
void pay() {
//implement payment1
}
}
class Swift implements PaymentProvider {
void pay() {
//implement payment2
}
}
Now if for some reason any payment system implementation process is changed, should not we have to modify the relevant class? For example, if we implement PayPal and after 2-3 years PayPal's working process is changed, does not modifying the PayPal class break the open/closed principle?
No, it doesn't break it. If the working process of PayPal changes it has to be reflected in the class that extends PayPal payment method.
I'll give you an example: Let's say that tomorrow you want to add one more payment method - Transferwise now the pattern says to us that "software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification" meaning that if you need to modify any existing class in order to add new payment method you're breaking an open/closed principle and on the other side if you can just extend PaymentMethod in your new Transferwise class you are extending your system without any change and you are complying with the pattern
I think the question here boils down to the definition of the Open/Closed Principle. Specifically, does it really mean that code should never change after it's written?
While many people (myself included) have used that definition as a substitute for the OCP, it's an oversimplification. The OCP was originally published by Bertrand Meyer in Object-Oriented Software Construction. I think the answer to this question can be found in the second edition of the book, beginning on page 60, where some "exceptions" to the OCP are noted.
If you have control over the original software and can rewrite it so that it will address the needs of several kinds of client at no extra complication, you should do so.
Neither the Open-Closed principle nor redefinition in inheritance is a way to address
design flaws, let alone bugs. If there is something wrong with a module, you should
fix it — not leave the original as it is and try to correct the problem in a derived
module... The Open-Closed principle and associated
techniques are intended for the adaptation of healthy modules: modules that,
although they may not suffice for some new uses, meet their own well-defined
requirements, to the satisfaction of their own clients.
Clearly, Meyer did not intend that legacy code should never be rewritten. If new requirements invalidate part of the existing logic, rewriting it may be a sensible approach and was never meant to be prohibited by the OCP.

SOLID - are the Single Responsibility Principle and the Open/Closed Principle mutually exclusive?

The Single Responsibility Principle states that:
A class should have one, and only one, reason to change.
The Open/Closed Principle states that:
You should be able to extend a classes behavior, without modifying it.
How can a developer respect both principles if a class should have only one reason to change, but should not be modified?
Example
The factory pattern is a good example here of something that has a single responsibility, but could violate the open/closed principle:
public abstract class Product
{
}
public class FooProduct : Product
{
}
public class BarProduct : Product
{
}
public class ProductFactory
{
public Product GetProduct(string type)
{
switch(type)
{
case "foo":
return new FooProduct();
case "bar":
return new BarProduct();
default:
throw new ArgumentException(...);
}
}
}
What happens when I need to add ZenProduct to the factory at a later stage?
Surely this violates the open/closed principle?
How can we prevent this violation?
This feels like a discussion of the semantics of 'extend a classes behaviour'. Adding the new type to the factory is modifying existing behaviour, it's not extending behaviour, because we haven't changed the one thing the factory does. We may need to extend the factory but we have not extended it's behaviour. Extending behaviour means introducing new behaviour and would be more along the lines of an event each time an instance of a type is created or authorising the caller of the factory - both these examples extend (introduce new) behaviour.
A class should have one, and only one, reason to change.
The example in the question is a factory for creating Product instances and the only valid reason for it to change is to change something about the Product instances it creates, such as adding a new ZenProduct.
You should be able to extend a classes behavior, without modifying it.
A really simple way to achieve this is through the use of a Decorator
The decorator pattern is often useful for adhering to the Single Responsibility Principle, as it allows functionality to be divided between classes with unique areas of concern.
public interface IProductFactory
{
Product GetProduct(string type);
}
public class ProductFactory : IProductFactory
{
public Product GetProduct(string type)
{
\\ find and return the type
}
}
public class ProductFactoryAuth : IProductFactory
{
IProductFactory decorated;
public ProductFactoryAuth(IProductFactory decorated)
{
this.decorated = decorated;
}
public Product GetProduct(string type)
{
\\ authenticate the caller
return this.decorated.GetProduct(type);
}
}
The decorator pattern is a powerful pattern when applying the SOLID principles. In the above example we've added authentication to the ProductFactory without changing the ProductFactory.
A class should have one, and only one, reason to change.
This basically means, your class should represent single responsibility and shouldn't be modified thereafter to accommodate new feature.
For example, if you have class, which is responsible to print report in pdf format. Later, you wanted to add new feature to support printing report in other formats. Then instead of modify the existing code, you should extend it to support other format, which also implies extend a classes behavior, without modifying it
I think it depends on your interpretation of the SRP. This stuff is always somewhat subjective. Ask 100 people to define "single responsibility" and you'll probably get 100 different answers.
Using the scenario in Ravi's answer, a typical solution might be to have a ReportGenerator class which exposes a GeneratePdf method. It could then be later extended with an additional GenerateWord method if required. Like yourself though, I think this has a whiff about it.
I would probably refactor the GeneratePdf method into a PdfReportGenerator class and then expose that through the ReportGenerator. That way the ReportGenerator only has a single responsibility; which is to expose the various report generation mechanisms (but not contain their logic). It could then be extended without expanding upon that responsibility.
I'd say that if you find a conflict, it might well be an architectural smell that warrants a quick review to see if it can be done in a better way.
I have a class StudentOrganiser class which takes IStudentRepository dependency. Interfaces exposed by IStudentRepository is say GetStudent(int studentId)
Class obeys SRP because it does not have any logic related to manage the connection with repository source.
Class obeys OCP because if we want to change repository source from SQL to XML, StudentOrganiser need not to undergo any changes => open for extension but closed for modification.
Consider if StudentOrganiser was designed to not take dependency of IStudentRepository, then method inside class itself must be taking care of instantiating new StudentSqlRepository() If later on requirement would have come to also support StudentXMLRepository on the basis of certain run time condition, your method would have ended with some case switch kind of paradigm and thus violating SRP as method is also indulged in actual repository deciding factor. By injecting repository dependency we taken off that responsibility from class. Now StudentOrganiser class can be extended to support StudentXMLRepository without any modification.

Base class has one responsibility, and derived class has another responsibility. Does this conforms to SRP?

I've a VolumeButton which derives from ButtonModifier. If I put my business logic (volume up/down, mute, etc) into VolumeButton, enable/disable logic to base class ButtonModifier. Like,
public class VolumeButton : ButtonModifier
{
/// Event handler to change the volume.
void ChangeVolume() { ... }
}
public class ButtonModifier
{
/// Updates the visibility of the button.
void UpdateVolumeVisibleStatus() { ... }
/// Enable or disable the button.
void Enable(bool enable) { ... }
}
On one hand, only business logic change affects VolumeButton, and infrastructure change affects ButtonModifier. So it conforms to SRP. On the other hand, VolumeButton inherited the enable/disable logic from base class. So it has two responsibilities?
Does these two class conform to SRP?
Both VolumeButton and ButtonModifier has only one reason to change separately. So they literally conform to the SRP. However, intuitively I think VolumeButton inherits the responsibilities from ButtonModifie, which cause it to violate the SRP.
From the perspective of code dependencies, you did not decouple any dependency by moving one responsibility from the derived class to the base class. According to Uncle Bob's "Agile Principles, Patterns and Practices in C#" Chapter 8, in all the examples, Uncle Bob introduced extra abstractions to separate the responsibilities. So a helpful hint to your question could be "Have you introduced extra abstraction to decouple the dependencies?"
I don't think they are conform to SRP, because derived class gets all the public/protected members from its base class, for instance:
public class Flyable
{
public void fly(){...}
}
public class Bird : Flyable
{
public void walk(){...}
}
the Bird can fly and walk.
Maybe the example is not exactly for your case. But what I'm want to say is that the derived class will get two responsibilities if the two responsibilities are separated with one in base and another in derived.

What is the difference between the ISP and the OCP?

I don't understand what the difference is between the Interface Segregation Principle and the Open/Closed Principle.
What I understand is that the ISP must make everything depend on interfaces and the OCP on classes and I see that both of them can be implemented in the same way but one with interfaces and one with classes.
Programming to interfaces tell us that our code should depend on interfaces , while ISP guides us to not create god interfaces that have large amount of methods. Large interfaces causes two major problems:
clients using that interface depend on methods that they don't use
new implementations of that interface must implement every method , and if interface is large it's not always clear how to properly implement that interface
OCP guides us to write code that we can extend without modifying existing code. So, for example let assume that you have following interface:
public interface ITask
{
void Execute();
}
Then you create SendToEmailTask class that implements ITask interface.
Assume that after some time new requirement shows up that you need add logging to SendToEmailTask. And according to OCP you should not modify existing code but rather add new LoggingTask that also implements ITask interface (using Decorator pattern):
public class LoggingTask : ITask
{
private readonly ITask task;
public LoggingTask(ITask task)
{
//guard clause
this.task = task;
}
public void Execute()
{
Logger.Log("task...");
this.task.Execute();
}
}
Thanks to that you also achieved Single Responsibility principle.
Lets look at principles from a perspective of a unit test development.
If you write a lot of unit tests for a single interface/class, it's like you violate the ISP principle. The interface is a too large.
If you want to override some method in testing interface/class, but you can't do it cause this method is not virtual, it's like you violate the OCP principle and your class doesn't allow an extention.

What is the meaning and reasoning behind the Open/Closed Principle?

The Open/Closed Principle states that software entities (classes, modules, etc.) should be open for extension, but closed for modification. What does this mean, and why is it an important principle of good object-oriented design?
It means that you should put new code in new classes/modules. Existing code should be modified only for bug fixing. New classes can reuse existing code via inheritance.
Open/closed principle is intended to mitigate risk when introducing new functionality. Since you don't modify existing code you can be assured that it wouldn't be broken. It reduces maintenance cost and increases product stability.
Specifically, it is about a "Holy Grail" of design in OOP of making an entity extensible enough (through its individual design or through its participation in the architecture) to support future unforseen changes without rewriting its code (and sometimes even without re-compiling **).
Some ways to do this include Polymorphism/Inheritance, Composition, Inversion of Control (a.k.a. DIP), Aspect-Oriented Programming, Patterns such as Strategy, Visitor, Template Method, and many other principles, patterns, and techniques of OOAD.
** See the 6 "package principles", REP, CCP, CRP, ADP, SDP, SAP
More specifically than DaveK, it usually means that if you want to add additional functionality, or change the functionality of a class, create a subclass instead of changing the original. This way, anyone using the parent class does not have to worry about it changing later on. Basically, it's all about backwards compatibility.
Another really important principle of object-oriented design is loose coupling through a method interface. If the change you want to make does not affect the existing interface, it really is pretty safe to change. For example, to make an algorithm more efficient. Object-oriented principles need to be tempered by common sense too :)
Open Closed Principle is very important in object oriented programming and it's one of the SOLID principles.
As per this, a class should be open for extension and closed for
modification. Let us understand why.
class Rectangle {
public int width;
public int lenth;
}
class Circle {
public int radius;
}
class AreaService {
public int areaForRectangle(Rectangle rectangle) {
return rectangle.width * rectangle.lenth;
}
public int areaForCircle(Circle circle) {
return (22 / 7) * circle.radius * circle.radius;
}
}
If you look at the above design, we can clearly observe that it's not
following Open/Closed Principle. Whenever there is a new
shape(Tiangle, Square etc.), AreaService has to be modified.
With Open/Closed Principle:
interface Shape{
int area();
}
class Rectangle implements Shape{
public int width;
public int lenth;
#Override
public int area() {
return lenth * width;
}
}
class Cirle implements Shape{
public int radius;
#Override
public int area() {
return (22/7) * radius * radius;
}
}
class AreaService {
int area(Shape shape) {
return shape.area();
}
}
Whenever there is new shape like Triangle, Square etc. you can easily
accommodate the new shapes without modifying existing classes. With
this design, we can ensure that existing code doesn't impact.
Software entities should be open for extension but closed for modification
That means any class or module should be written in a way that it can be used as is, can be extended, but neve modified
Bad Example in Javascript
var juiceTypes = ['Mango','Apple','Lemon'];
function juiceMaker(type){
if(juiceTypes.indexOf(type)!=-1)
console.log('Here is your juice, Have a nice day');
else
console.log('sorry, Error happned');
}
exports.makeJuice = juiceMaker;
Now if you want to add Another Juice type, you have to edit the module itself, By this way, we are breaking OCP .
Good Example in Javascript
var juiceTypes = [];
function juiceMaker(type){
if(juiceTypes.indexOf(type)!=-1)
console.log('Here is your juice, Have a nice day');
else
console.log('sorry, Error happned');
}
function addType(typeName){
if(juiceTypes.indexOf(typeName)==-1)
juiceTypes.push(typeName);
}
function removeType(typeName){
let index = juiceTypes.indexOf(typeName)
if(index!==-1)
juiceTypes.splice(index,1);
}
exports.makeJuice = juiceMaker;
exports.addType = addType;
exports.removeType = removeType;
Now, you can add new juice types from outside the module without editing the same module.
Let's break down the question in three parts to make it easier to understand the various concepts.
Reasoning Behind Open-Closed Principle
Consider an example in the code below. Different vehicles are serviced in a different manner. So, we have different classes for Bike and Car because the strategy to service a Bike is different from the strategy to service a Car. The Garage class accepts various kinds of vehicles for servicing.
Problem of Rigidity
Observe the code and see how the Garage class shows the signs of rigidity when it comes to introducing a new functionality:
class Bike {
public void service() {
System.out.println("Bike servicing strategy performed.");
}
}
class Car {
public void service() {
System.out.println("Car servicing strategy performed.");
}
}
class Garage {
public void serviceBike(Bike bike) {
bike.service();
}
public void serviceCar(Car car) {
car.service();
}
}
As you may have noticed, whenever some new vehicle like Truck or Bus is to be serviced, the Garage will need to be modified to define some new methods like serviceTruck() and serviceBus(). That means the Garage class must know every possible vehicle like Bike, Car, Bus, Truck and so on. So, it violates the open-closed principle by being open for modification. Also it's not open for extension because to extend the new functionality, we need to modify the class.
Meaning Behind Open-Closed Principle
Abstraction
To solve the problem of rigidity in the code above we can use the open-closed principle. That means we need to make the Garage class dumb by taking away the implementation details of servicing of every vehicle that it knows. In other words we should abstract the implementation details of the servicing strategy for each concrete type like Bike and Car.
To abstract the implementation details of the servicing strategies for various types of vehicles we use an interface called Vehicle and have an abstract method service() in it.
Polymorphism
At the same time, we also want the Garage class to accept many forms of the vehicle, like Bus, Truck and so on, not just Bike and Car. To do that, the open-closed principle uses polymorphism (many forms).
For the Garage class to accept many forms of the Vehicle, we change the signature of its method to service(Vehicle vehicle) { } to accept the interface Vehicle instead of the actual implementation like Bike, Car etc. We also remove the multiple methods from the class as just one method will accept many forms.
interface Vehicle {
void service();
}
class Bike implements Vehicle {
#Override
public void service() {
System.out.println("Bike servicing strategy performed.");
}
}
class Car implements Vehicle {
#Override
public void service() {
System.out.println("Car servicing strategy performed.");
}
}
class Garage {
public void service(Vehicle vehicle) {
vehicle.service();
}
}
Importance of Open-Closed Principle
Closed for modification
As you can see in the code above, now the Garage class has become closed for modification because now it doesn't know about the implementation details of servicing strategies for various types of vehicles and can accept any type of new Vehicle. We just have to extend the new vehicle from the Vehicle interface and send it to the Garage. That's it! We don't need to change any code in the Garage class.
Another entity that's closed for modification is our Vehicle interface.
We don't have to change the interface to extend the functionality of our software.
Open for extension
The Garage class now becomes open for extension in the context that it will support the new types of Vehicle, without the need for modifying.
Our Vehicle interface is open for extension because to introduce any new vehicle, we can extend from the Vehicle interface and provide a new implementation with a strategy for servicing that particular vehicle.
Strategy Design Pattern
Did you notice that I used the word strategy multiple times? That's because this is also an example of the Strategy Design Pattern. We can implement different strategies for servicing different types of Vehicles by extending it. For example, servicing a Truck has a different strategy from the strategy of servicing a Bus. So we implement these strategies inside the different derived classes.
The strategy pattern allows our software to be flexible as the requirements change over time. Whenever the client changes their strategy, just derive a new class for it and provide it to the existing component, no need to change other stuff! The open-closed principle plays an important role in implementing this pattern.
That's it! Hope that helps.
It's the answer to the fragile base class problem, which says that seemingly innocent modifications to base classes may have unintended consequences to inheritors that depended on the previous behavior. So you have to be careful to encapsulate what you don't want relied upon so that the derived classes will obey the contracts defined by the base class. And once inheritors exist, you have to be really careful with what you change in the base class.
Purpose of the Open closed Principle in SOLID Principles is to
reduce the cost of a business change requirement.
reduce testing of existing code.
Open Closed Principle states that we should try not to alter existing
code while adding new functionalities. It basically means that
existing code should be open for extension and closed for
modification(unless there is a bug in existing code). Altering existing code while adding new functionalities requires existing features to be tested again.
Let me explain this by taking AppLogger util class.
Let's say we have a requirement to log application wide errors to a online tool called Firebase. So we create below class and use it in 1000s of places to log API errors, out of memory errors etc.
open class AppLogger {
open fun logError(message: String) {
// reporting error to Firebase
FirebaseAnalytics.logException(message)
}
}
Let's say after sometime, we add Payment Feature to the app and there is a new requirement which states that only for Payment related errors we have to use a new reporting tool called Instabug and also continue reporting errors to Firebase just like before for all features including Payment.
Now we can achieve this by putting an if else condition inside our existing method
fun logError(message: String, origin: String) {
if (origin == "Payment") {
//report to both Firebase and Instabug
FirebaseAnalytics.logException(message)
InstaBug.logException(message)
} else {
// otherwise report only to Firebase
FirebaseAnalytics.logException(message)
}
}
Problem with this approach is that it violates Single Responsibility Principle which states that a method should do only one thing. Another way of putting it is a method should have only one reason to change. With this approach there are two reasons for this method to change (if & else blocks).
A better approach would be to create a new Logger class by inheriting the existing Logger class like below.
class InstaBugLogger : AppLogger() {
override fun logError(message: String) {
super.logError(message) // This uses AppLogger.logError to report to Firebase.
InstaBug.logException(message) //Reporting to Instabug
}
}
Now all we have to do is use InstaBugLogger.logError() in Payment features to log errors to both Instabug and Firebase. This way we reduce/isolate the testing of new error reporting requirement to only Payment feature as code changes are done only in Payment Feature. The rest of the application features need not be tested as there are no code changes done to the existing Logger.
The principle means that it should easy to add new functionality without having to change existing, stable, and tested functionality, saving both time and money.
Often, polymorhism, for instance using interfaces, is a good tool for achieving this.
An additional rule of thumb for conforming to OCP is to make base classes abstract with respect to functionality provided by derived classes. Or as Scott Meyers says 'Make Non-leaf classes abstract'.
This means having unimplemented methods in the base class and only implement these methods in classes which themselves have no sub classes. Then the client of the base class cannot rely on a particular implementation in the base class since there is none.
I just want to emphasize that "Open/Closed", even though being obviously useful in OO programming, is a healthy method to use in all aspects of development. For instance, in my own experience it's a great painkiller to use "Open/Closed" as much as possible when working with plain C.
/Robert
This means that the OO software should be built upon, but not changed intrinsically. This is good because it ensures reliable, predictable performance from the base classes.
I was recently given an additional idea of what this principle entails: that the Open-Closed Principle describes at once a way of writing code, as well as an end-result of writing code in a resilient way.
I like to think of Open/Closed split up in two closely-related parts:
Code that is Open to change can either change its behavior to correctly handle its inputs, or requires minimum modification to provide for new usage scenarios.
Code that is Closed for modification does not require much if any human intervention to handle new usage scenarios. The need simply does not exist.
Thus, code that exhibits Open/Closed behavior (or, if you prefer, fulfills the Open/Closed Principle) requires minimal or no modification in response to usage scenarios beyond what it was originally built for.
As far as implementation is concerned? I find that the commonly-stated interpretation, "Open/Closed refers to code being polymorphic!" to be at best an incomplete statement. Polymorphism in code is one tool to achieve this sort of behavior; Inheritance, Implementation...really, every object-oriented design principle is necessary to write code that is resilient in the way implied by this principle.
In Design principle, SOLID – the "O" in "SOLID" stands for the open/closed principle.
Open Closed principle is a design principle which says that a class, modules and functions should be open for extension but closed for modification.
This principle states that the design and writing of the code should be done in a way that new functionality should be added with minimum changes in the existing code (tested code). The design should be done in a way to allow the adding of new functionality as new classes, keeping as much as possible existing code unchanged.
Benefit of Open Closed Design Principle:
Application will be more robust because we are not changing already tested class.
Flexible because we can easily accommodate new requirements.
Easy to test and less error prone.
My blog post on this:
http://javaexplorer03.blogspot.in/2016/12/open-closed-design-principle.html