Saturday, July 11, 2015

Open Closed Design Principle



https://miafish.wordpress.com/2015/01/09/openclose-principle/
The ways to implement the Open/Close Principle are Strategy Pattern andTemplate Pattern
The Open Close 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. 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.
Software entities like classes, modules and functions should be open for extension but closed for modifications.
Based only on the schema above, one can deduce that any class directly using another class would actually violate the Open/Closed Principle. And that is right, strictly speaking. I found it quite interesting to find the limits, the moment when you draw the line and decide that it is more difficult to respect OCP than modify existing code, or the architectural cost does not justify the cost of changing existing code.
Open Close Principle(OCP) - bad
Open Close Principle(OCP) - good
The ways to implement the Open/Close Principle are Strategy Pattern andTemplate Pattern
http://javarevisited.blogspot.com/2011/11/great-example-of-open-closed-design.html
new functionality should be added by introducing new classes, methods or fields instead of modifying already tried and tested code.
One of the way to achieve this is Inheritance where class is extended to introduce new functionality on top of inherited basic features.

Basic principle of making your code extensible and following open closed principle is providing object to class at run time and making use of polymorphism to invoke extended functionality.
If functionality is hard Coded than it wouldn’t be extensible but if you write interface and provide implementation of that interface at run time you make it extensible.

http://blog.sanaulla.info/2011/11/19/solid-open-closed-principle/
you don’t touch the existing modules thereby not disturbing the existing functionality, instead you extend the modules to implement the new requirement. S

Allow the modules (classes) to depend on the abstractions, there by new features can be added by creating new extensions of these abstractions.

Avoiding typecasts at runtime- This makes the code fragile and dependent on the classes under consideration, which means any new class might require editing the method to accommodate the cast for the new class.
http://thinkinginobjects.com/2012/09/24/open-closed-principle/
When the requirements of an application changes, if the application confirms to OCP, we can extend the existing modules with new behaviours to satisfy the changes (Open for extension). Extending the behaviour of the existing modules does not result in changes to the source code of the existing modules (Closed for modification). Other modules that depends on the extended modules are not affected by the extension. Therefore we don’t need to recompile and retest them after the change. The scope of the change is localised and much easier to implement.
The key of OCP is to place useful abstractions (abstract classes/interfaces) in the code for future extensions. However it is not always obvious that what abstractions are necessary. It can lead to over complicated software if we add abstractions blindly. I found Robert C Martin’s “Fool me once” attitude very useful. I start my code with minimal number of abstractions. When a change of requirements takes place, I modify the code to add an abstraction and protect myself from future changes of the similar kind.
public class MessageSender {
 
    private Transport transport;
 
    public synchronized void send(Message message) throws IOException{
        byte[] bytes = message.toBytes();
        transport.sendBytes(bytes);
    }
}
After the code was deployed to production, we found out that we sent messages too fast that the transport cannot handle. However the transport was optimised for handling large messages, I modified the MessageSender to send messages in batches of size of ten.
ublic class MessageSenderWithBatch {
 
    private static final int BATCH_SIZE = 10;
 
    private Transport transport;
 
    private List buffer = new ArrayList();
 
    private ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
 
    public MessageSenderWithBatch(Transport transport) {
        this.transport = transport;
    }
 
    public synchronized void send(Message message) throws IOException {
        buffer.add(message);
        if (buffer.size() == BATCH_SIZE) {
            sendBuffer();
        }
    }
 
    private void sendBuffer() throws IOException {
        for (Message each : buffer) {
            byte[] bytes = each.toBytes();
            byteStream.write(bytes);
        }
        byteStream.flush();
        transport.sendBytes(byteStream.toByteArray());
        byteStream.reset();
    }
 
}
The solution was simple but I hesitated to commit to it. There were two reasons:
  1. MessageSender class need to be modified if we change how messages are batched in the future. It violated the Open-Closed Principle.
  2. MessageSender had secondary responsibility to batch messages in addition to the responsibility of convert/delegate messages. It violated the Single Responsibility Principle.
Therefore I created a BatchingStrategy abstraction, who was solely responsible for deciding how message are batched together. It can be extended by different implementations if the batch strategy changes in the future. In another word, the module was open for extensions of different batch strategy. The MessageSender kept its single responsibility that converting/delegating messages, which means it does not get modified if similar changes happen in the future. The module was closed for modification.
public class MessageSenderWithStrategy {
 
    private Transport transport;
 
    private BatchStrategy strategy;
 
    private ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
 
    public synchronized void send(Message message) throws IOException {
        strategy.newMessage(message);
        List buffered = strategy.getMessagesToSend();
        sendBuffer(buffered);
        strategy.sent();
    }
 
    private void sendBuffer(List buffer) throws IOException {
        for (Message each : buffer) {
            byte[] bytes = each.toBytes();
            byteStream.write(bytes);
        }
        byteStream.flush();
        transport.sendBytes(byteStream.toByteArray());
        byteStream.reset();
    }
}
public class FixSizeBatchStrategy implements BatchStrategy {
 
    private static final int BATCH_SIZE = 0;
    private List buffer = new ArrayList();
 
    @Override
    public void newMessage(Message message) {
        buffer.add(message);
    }
 
    @Override
    public List getMessagesToSend() {
        if (buffer.size() == BATCH_SIZE) {
            return buffer;
        } else {
            return Collections.emptyList();
        }
    }
 
    @Override
    public void sent() {
        buffer.clear();
    }
}
The patch was successful, but two weeks later we figured out that we can batch the messages together in time slices and overwrite outdated messages with newer version in the same time slice. The solution was specific to our business domain of publishing market data.


More importantly, the OCP showed its benefits when we implemented the change. We only needed to extend the existing BatchStrategy interface with an different implementation. We didn’t change a single line of code but the spring configuration file.
public class FixIntervalBatchStrategy implements BatchStrategy {
 
    private static final long INTERVAL = 5000;
 
    private List buffer = new ArrayList();
 
    private volatile boolean readyToSend;
 
    public FixIntervalBatchStrategy() {
        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
        executorService.scheduleAtFixedRate(new Runnable() {
 
            @Override
            public void run() {
                readyToSend = true;
            }
        }, 0, INTERVAL, TimeUnit.MILLISECONDS);
    }
 
    @Override
    public void newMessage(Message message) {
        buffer.add(message);
    }
 
    @Override
    public List getMessagesToSend() {
        if (readyToSend) {
            List toBeSent = buffer;
            buffer = new ArrayList();
            return toBeSent;
        } else {
            return Collections.emptyList();
        }
    }
 
    @Override
    public void sent() {
        readyToSend = false;
        buffer.clear();
    }
}
The Open-Closed Principle serves as an useful guidance for writing good quality module that is easy to change and maintain. We need to be careful not to create too many abstractions prematurely. It is worth to defer the creation of abstractions to the time when the change of requirement happens. However, when the changes strike, don’t hesitate to create an abstraction and make the module to confirm OCP. There is a great chance that a similar change of the same kind is at your door step.
https://lostechies.com/gabrielschenker/2009/02/13/the-open-closed-principle/
http://blog.csdn.net/zhengzhb/article/details/7296944
定义:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。
问题由来:在软件的生命周期内,因为变化、升级和维护等原因需要对软件原有代码进行修改时,可能会给旧代码中引入错误,也可能会使我们不得不对整个功能进行重构,并且需要原有代码经过重新测试。
解决方案:当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。

         其实笔者认为,开闭原则无非就是想表达这样一层意思:用抽象构建框架,用实现扩展细节。因为抽象灵活性好,适应性广,只要抽象的合理,可以基本保持软件架构的稳定。而软件中易变的细节,我们用从抽象派生的实现类来进行扩展,当软件需要发生变化时,我们只需要根据需求重新派生一个实现类来扩展就可以了。当然前提是我们的抽象要合理,要对需求的变更有前瞻性和预见性才行。
         说到这里,再回想一下前面说的5项原则,恰恰是告诉我们用抽象构建框架,用实现扩展细节的注意事项而已:单一职责原则告诉我们实现类要职责单一;里氏替换原则告诉我们不要破坏继承体系;依赖倒置原则告诉我们要面向接口编程;接口隔离原则告诉我们在设计接口的时候要精简单一;迪米特法则告诉我们要降低耦合。而开闭原则是总纲,他告诉我们要对扩展开放,对修改关闭。

http://tianweili.github.io/blog/2015/02/15/open-close-principle/
Software entities like classes, modules and functions shoule be open for extension but closed for modifications.(一个软件实体如类,模块和函数应该对扩展开放,对修改关闭。)
开闭原则的定义很短,就是对扩展开放,对修改关闭。但是为什么要遵守这一个原则呢?
做过实际项目的筒子们应该都会深有体会,一个软件在其生命周期内都会发生很多变化,这几乎是不可避免的。无论是需求的变化、业务逻辑的变化、程序代码的变化等等,这些变化都有可能对整个软件的稳定性造成一定的威胁。
而开闭原则就是应对这些变化的,它告诉我们应该通过扩展来实现变化,而不是通过修改已有的代码。
public class LevelStudent extends Student {

  public LevelStudent(String name, String grade) {
      super(name, grade);
  }

  @Override
  public String getGrade() {
      String level = null;
      int grade_ = Integer.valueOf(super.getGrade());
      if (grade_ >= 90) {
          level = "优秀";
      } else if (grade_ >= 80 && grade_ < 90) {
          level = "良好";
      } else if (grade_ >= 70 && grade_ < 80) {
          level = "一般";
      } else if (grade_ >= 60 && grade_ < 70) {
          level = "及格";
      } else if (grade_ < 60) {
          level = "不及格";
      }
      return level;
  }
}

开闭原则是对扩展开放,对修改关闭。
开闭原则的主旨是为了拥抱变化。
在六大原则中,开闭原则只是一个思想,没有具体实际操作方法。其他五大原则都是为了实现这个开闭思想的一些方法和工具。
想要遵守开闭原则,就需要一个设计合理的系统。可以说在做系统设计的时候就要考虑到未来的扩展和改变。

Labels

Review (572) System Design (334) System Design - Review (198) Java (189) Coding (75) Interview-System Design (65) Interview (63) Book Notes (59) Coding - Review (59) to-do (45) Linux (43) Knowledge (39) Interview-Java (35) Knowledge - Review (32) Database (31) Design Patterns (31) Big Data (29) Product Architecture (28) MultiThread (27) Soft Skills (27) Concurrency (26) Cracking Code Interview (26) Miscs (25) Distributed (24) OOD Design (24) Google (23) Career (22) Interview - Review (21) Java - Code (21) Operating System (21) Interview Q&A (20) System Design - Practice (20) Tips (19) Algorithm (17) Company - Facebook (17) Security (17) How to Ace Interview (16) Brain Teaser (14) Linux - Shell (14) Redis (14) Testing (14) Tools (14) Code Quality (13) Search (13) Spark (13) Spring (13) Company - LinkedIn (12) How to (12) Interview-Database (12) Interview-Operating System (12) Solr (12) Architecture Principles (11) Resource (10) Amazon (9) Cache (9) Git (9) Interview - MultiThread (9) Scalability (9) Trouble Shooting (9) Web Dev (9) Architecture Model (8) Better Programmer (8) Cassandra (8) Company - Uber (8) Java67 (8) Math (8) OO Design principles (8) SOLID (8) Design (7) Interview Corner (7) JVM (7) Java Basics (7) Kafka (7) Mac (7) Machine Learning (7) NoSQL (7) C++ (6) Chrome (6) File System (6) Highscalability (6) How to Better (6) Network (6) Restful (6) CareerCup (5) Code Review (5) Hash (5) How to Interview (5) JDK Source Code (5) JavaScript (5) Leetcode (5) Must Known (5) Python (5)

Popular Posts