Confusion about open/closed principal - oop

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.

Related

Open-closed principle VS DRY

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?

What exactly is a "client" in the interface segregation principle (ISP)?

This is one of a number of things that's been bugging me for a while and for which quibbling over the correct interpretation of this has been leading me in a number of attempted coding projects to more fussing around with design, than it has with making steady forward progress, because I want to make sure I've gotten it "right".
In this case, I have a question about the "interface segregation principle" (ISP) of the five "SOLID" principles of basic object oriented design. Its "canonical" statement is this:
Clients should not be forced to depend on methods they do not use.
However, the question here is, is what is the "client" - because this leads to very different interpretations, and also leads to a seeming dilemma when applied in practice, which is what I'd like to understand if first actually is one, and second, how to best solve it.
One interpretation of this is that it is a "single responsibility principle" for interfaces instead of just classes: i.e. if you have an interface IMessageReceiver, it better not include a Send method. Another possible interpretation is that it means that the implementer of an interface should not be required to implement empty methods, while a third interpretation is that the caller should not be able to see any more methods than it actually needs. And the thing is, I can see merit in all three of these interpretations, yet when you apply them all, it seems that in any suitably large project, highly counterintuitive and seemingly problematic things result. In particular, it's that third one that seems to be the rub.
For one, if something gets used in enough places, it is particularly that last one - the "caller" one - which generally tends to "bite" in that it results naturally in your interfaces being atomized down to single methods only. For example, consider a simple interface to a backend storage or database, which may have a load, save, and update method. Some callers of that, though, may not want to save anything. They may just want to peek at the data. Hence to avoid violating the caller interpretation, we must split off the load method. Add a few more use cases and now it's atomized into a IDataLoader, IDataSaver, and IDataUpdater which all have 1 method each.
And I can't help but feel this almost seems like an anti-pattern, particularly if it happens with enough objects owing to them being used in a suitably wide variety of places. Is it? Or am I misinterpreting something here? After all, nobody makes a generic container class (like the "list", "map", etc. things in a lot of languages) with single-method interfaces to be piecemealed out to whatever it gets passed to.
One interpretation of this is that it is a "single responsibility principle" for interfaces...
Robert Martin has addressed this question directly, and his answer is that ISP separates things that SRP does not, thus the two principles differ.
Another possible interpretation is that it means that the implementer of an interface...
We also see in his answer that the client is the caller of the interface, not the implementer. I've touched on that in another answer regarding default methods. The implementer should be implementing exactly as many methods as the caller needs: no more, no less.
a third interpretation is that the caller should not be able to see any more methods than it actually needs.
This is correct, and it is also correct to note that a single-method interface cannot violate the ISP. While this may result in a "lowest-common-denominator" approach where every interface is a single method, they should be highly composable in that case; i.e. implementations can select several interfaces to implement, making them highly customizable to client needs.
The database example of separating read from write is so common that is has its own pattern: CQRS. There are arguments both ways. Likewise, generic collections may choose to avoid creating too many interfaces, which is a design counter to the ISP.
I would suggest that in most applications (particularly service applications as opposed to libraries) if rigorously applying the ISP results exclusively in single-method interfaces, it indicates the clients are not cohesive and not complex. If this is an accurate description of the clients, then single-method interfaces are probably appropriate. You may also consider a different architecture such as a separate service for each client if their requirements have so little overlap.
When somebody wants to develop a feature, then he/she tries to map real world object to unit where code can be put. It is done to imitate behavior.
This unit can be called class. And this class has to have just one feature. It is place where we use SRP.
E.g., if programmer maps real world object person to class Person.
Then this class Person does not have to have methods/behavior of other objects. E.g. the Person class does not have to have methods of Car class. This is what SRP about.
Clients should not be forced to depend on methods they do not use.
We've created a Person class so far. Now we want to log all speech of person. So we need to log it. How? We can use interface ILogging with one method Log. Why does this interface have just one method?
Because clients will only have to know about the methods that are of interest to them.
Here Person class is client of interface ILogging.
SRP means that a class is responsible just for one feature. E.g. if you want to edit logging in Person class, then it is violation of SRP.
public class Person
{
public string FirstName { get; set; }
public void Say(string message)
{
File.WriteAllText("path", message); // violation of SRP
}
}
How can we avoid violation of SRP? We should create an abstraction of logging:
public interface ILogging
{
void Write(string address, string content);
}
and use it:
public class PersonWithSRP
{
private readonly ILogging _logging;
public string FirstName { get; set; }
public PersonWithSRP(ILogging logging)
{
_logging = logging;
}
public void Say(string message)
{
_logging.Write("path", message);
}
}
public class Logging : ILogging
{
public void Write(string address, string content)
{
File.WriteAllText(address, content);
}
}
By extracting logic of logging in separate class, we moved logic of logging in special, single class or place where we can edit only logic of logging on one place.
What is about ISP?
nobody makes a generic container class
yeah, you are right. Interface should have only necessary methods which client class needs.
An example with HDD that uses ISP:
public interface IReadable
{
string Read();
}
public interface IWriteable
{
void Write();
}
public class HDD : IReadable, IWriteable
{
public string Read() { }
public void Write() { }
}
By creating one interface for Read() and Write() methods, it would obligate class to implement both methods in class. But some classes only want to read data, others want to write data, and some to do both. So in this case it is better to create separate interfaces.
So let's look another example with CardReader. CardReader just reads data, it does not write data. So, if we inherit one interface with Read() and Write() methods, then we would violate ISP principle. An example of violation of ISP:
public interface IWriteReadable
{
string Read();
void Write();
}
public class CardReader : IWriteReadable
{
// this is a necessary method
public string Read() { }
// here we are obligating to implement unnecessary method of interface
public void Write() { }
}
So by applying ISP, you only puts methods in interface that are necessary for the client class. If your class/client just wants to read data, then you need to use IReadable interface, not IReadableWriteable.

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.

Using an interface/Inheritance as a marker when concrete type represents different choices of which there can only be one

Quite a mouth-full of a question but its a OO principle I've struggling with. Lets say i have an e-commerce app and there is the concept of payment method, examples could be CreditCard, Paypal, Apple pay etc. The user has a choice of which payment method to select so i need to present them all in a list on the screen and depending on the selection this will be used to drive a UI, presenting different text/images/interactions as well as will be serialised slightly differently into a Payment request over the wire.
Here is some code:
public class PaypalPayment : PaymentMethod {
public string Token;
public string String;
}
public class CreditCardPayment : PaymentMethod {
public Address Address;
public CreditCard CreditCard;
}
interface PaymentMethod {
}
public class Booking {
public PaymentMethod PaymentMethod; //generic object
//or
public PaypalPayment PaypalPayment;
public CreditCardPayment CreditCardPayment;
}
So in my booking class i can either have a generic payment object referred to by the interface but i cant get to the underlying type without casting which sucks, as in reality them don't share any common properties doman-wise. Alternatively i have multiple properties which feels bad in a different way. The user can only select one payment method so the others will have to null or some kind of null object, i would have to either query an enum or ask each payment method if it is null. Also my payment method select screen is a bit more cumbersome as i cant just iterate on a generic type i have to explicitly build up the list.
In theory i could add some methods to the PaymentMethod interface, such as Serialise() or some UI presentation methods which all the payment methods have in common but then i would have to implement them in my model object which i don't want to do in my model layer.
Overall i dont have a clean solution for this in your typical object orientated language. I wrote this in c# but this could apply to any OO language.
Data seems to have been segregated away from logic in your design. As a consequence, Booking probably has to indulge in Inappropriate Intimacy with its Payment in order for the behavior to take place, hence the casting problem.
An idiomatic OO implementation would 1/ define a clear responsibility and 2/ encapsulate operations and data for it in the same class. Then you can have an abstraction on top of a family of these classes so that their behavior can be called uniformly by consumer code.
The Strategy aka Policy pattern might be a good choice for payment.
UI wise, it may be better to avoid using abstractions and have different UIs altogether for different payment methods. Or, you could have an abstract UI payment model whose concrete implementations know how to render themselves.
To have an interface and can't get to the underlying type is exactly what abstraction and loose coppling is striving for. This should not suck but should be desirable.
To get all possible instances to choose from, you could use a repository that returns a collection of all your payment methods.
To implement this repository you could use your favored ORM and load them from the database, use your favored IoC/DI Container and let it create all implementors of your interface, or hard code the creation. Whatever suits you needs and the needs of the project. If you use an interface for the repository as well, you can later swap the implementation.

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