Design mode 6 principle

1. OCP–open close principle is the The cornerstone of “reuse design”.
The “open” in the opening and closing principle means that the expansion of the component function is open. When the demand changes, the original module can be expanded to meet the newly added demand;
The “closed” in the opening and closing principle means that the modification of the original function code is closed and prohibited.
Therefore, The key to realizing the principle of opening and closing is to use “abstraction”. All possible behaviors of the system are abstracted into an abstract bottom layer, which specifies the characteristics of the methods that must be provided for all detailed implementations. As the abstract layer of system design, all possible expansions should be foreseen. When the functions of the system need to be expanded, one or more new detailed implementations can be derived from the abstract bottom layer, which can change the behavior of the system.
If one The system conforms to the principle of opening and closing, so it should have the following advantages:
1) Reusable. After the software is developed, the software can still be expanded and new functions can be added without changing the original code of the system.
2) Good maintainability. For the original components of the system, there is no need to make changes, which ensures the stability of the original functions
div>


2. Single principle< /span>
Just one category In terms of it, it is only responsible for one responsibility. This requires the division of functions to be clearer.
The single responsibility principle is the simplest but the most difficult to apply. It requires designers to discover the different responsibilities of a class and separate them , And then encapsulated into different classes or modules. The multiple responsibilities of discovery require designers to have strong analysis and design capabilities and relevant refactoring experience.
Note: Single responsibility also applies to methods. A method should do one thing as best as possible. If a method handles too many things, its granularity will become very coarse, which is not conducive to reuse.

3. Richter replacement principle
All references to the base class must be able to transparently use the objects of its subclasses—subclasses The object of the parent class can be replaced, and the program logic remains unchanged.
The Richter substitution principle has different performances in the following two scenarios:
< span style="font-size: 16px;"> 1) The Richter substitution principle is for inheritance. If inheritance is for better code reuse, that is, method sharing, then the shared parent class method should remain unchanged Change, the subclass cannot be redefined. Subclasses can only add new methods to expand their functions. Both the parent class and the subclass can be instantiated. The inherited methods of the subclass are the same as the parent class. Where the parent class calls the method, the subclass can call the same inherited method. , At this time, the subclass method is used to replace the parent class method, and the logic is consistent.
2) If inheritance is to achieve polymorphism, the premise of polymorphism is that the subclass overrides and redefines the method of the parent class In order to comply with the Richter substitution principle, the parent class should be defined as an abstract class, and abstract methods should be defined, so that the subclass can redefine these methods. When the parent class is an abstract class, there is no instantiable parent class object in the program. There is no such thing as a subclass that can replace a parent class method.
Therefore, The scenario that does not conform to the Richter substitution principle is that the subclass and the parent class are both non-abstract classes, and the methods of the parent class are redefined by the subclass. If the new function is completed by rewriting the parent class method, although it is simple to write this way, the reusability of the entire inheritance system will be relatively poor, especially when polymorphism is used frequently, the probability of program operation errors will be very large . If the program violates the Richter substitution principle, the objects of the inherited class will run into errors wherever the base class appears. At this time, the correction method is: cancel the original inheritance relationship and redesign the relationship between them.
Take the following example, a square is defined as a kind of rectangle. When the square is set as a rectangular sub When class, in some cases, using the square method to replace the rectangular method of the parent class will make mistakes.
 /**

* Define a rectangular class with only standard get and set methods
*
*
@author sxh
*
*/
public class Rectangle {
  
protected long width;
  
protected long height;

public void setWidth(long width) {
  
this.width = width;
}

public long getWidth() {
  
return this.width;
}

public void setHeight(long height) {
  
this.height = height;
}

public long getHeight() {
  
return this.height;
}}

 /* *

* Define a square class inherited from a rectangular class, with only one side
*
*
@author sxh
*
*/
public class Square extends Rectangle {
  
public void setWidth(long width) {
    
this.height = width;
    
this.width = width;
  }

  
public long getWidth() {
    
return width;
  }

  
public void setHeight(long height) {
    
this.height = height;
    
this.width = height;
  }

  
public long getHeight() {
    
return height;
  }
}

public class SmartTest{
  
/**
  * The length of the rectangle does not increase until it exceeds the width
  *
@param r
  
*/
  public void resize(Rectangle r){
  
while (r.getHeight() <= r.getWidth() ){
    r.setHeight(r.getHeight()
+ 1);
  }
  }
}

   In the code above we define a rectangle and an inheritance The square from the rectangle looks very logical, but when we call the resize method in the SmartTest class, the rectangle is okay, but the square will keep growing, and the long overflows all the time. But we follow our Richter substitution principle, where the parent class can be replaced with a subclass, so the above example does not conform to the Richter substitution principle.

  Analyzed, we found that the subclass did not override or overload the method of the parent class. Therefore, this The inheritance relationship is not established at all. At this time, for this kind of relationship that does not conform to the principle of Richter substitution, we need to reconstruct their relationship.
share picture  image Share

We redefine a parent class so that both squares and rectangles inherit this parent class.
public abstract class Quadrangle {

protected abstract long getWidth();
protected abstract long getHeight();
}

/**
* Declare height and width yourself
*
@author sxh
*
*/
public class Rectangle extends Quadrangle {
  
private long width;
  
private long height;

  
public void setWidth(long width) {
    
this.width = width;
  }

  
public long getWidth() {
    
return this.width;
  }

  
public void setHeight(long height) {
    
this.height = height;
  }

  
public long getHeight() {
    
return this.height;
  }
}

/**

* Declare height and width yourself
*
@author xingjiarong
*
*/
public class Square extends Quadrangle{
  
private long width;
  
private long height;

  
public void setWidth(long width) {
    
this.height = width;
    
this.width = width;
  }

  
public long getWidth() {
    
return width;
  }

  
public void setHeight(long height) {
    
this.height = height;
    
this.width = height;
  }

  
public long getHeight() {
    
return height;
  }
}

There is no assignment method in the base class Quadrange Therefore, the resize() method similar to SamrtTest cannot be applied to the Quadrangle type, but can only be applied to different concrete subclasses Rectangle and Aqua, so the Richter substitution principle cannot be broken.


4. Combination reuse principle
The principle of composite reuse is achieved by incorporating existing objects into new objects as member objects of the new objects. The new objects can call the functions of the existing objects to achieve reuse.
1) Combination reuse has the following advantages:
  • The only way for the new object to access the component object is through the component object’s interface.
  • This reuse is a black box reuse, because the internal details of the component object are invisible.
  • This kind of reuse supports packaging.
  • This kind of reuse requires less dependency.
  • Each new class can focus on a task.
  • This reuse can be performed dynamically at runtime, and new objects can be dynamically referenced and composed Objects of the same object type.
The disadvantage of combined reuse is that there will be more systems built with combined reuse Objects need to be managed.
In addition to combining to achieve reuse, the inheritance mentioned before is also a means to achieve reuse. But inheritance will increase the coupling of the code. When the base class changes, its subclasses must change accordingly. According to the principle of combination reuse, combination should be preferred, followed by inheritance, and the Richter substitution principle should be strictly observed when using inheritance. Inheritance must be satisfied when the “IS-a” relationship is satisfied, and the combination is “Has-a” Relationship.

< div> 5. Dependency inversion principle

High-level modules should not rely on Low-level modules, both should rely on their abstraction. Abstraction should not depend on details, and details should depend on abstraction.
In the design of software, details are changeable, while abstraction is relatively stable. Therefore, a system based on abstraction is more Building on the basis of realization is more stable. The abstraction here refers to the interface or abstract class, and the realization refers to the concrete realization class.
The purpose of using interfaces and abstract classes is to formulate specifications and contracts instead of designing any specific operations and show details The task is handed over to the implementation class to complete.

public class Test1 {

// shop
interface Shop {
void sell();
}
// RT-Mart supermarket
class daRunFa implements Shop{
@Override
public void sell() {
System.out.println(
"Welcome to RT-Mart...");
}
}
// China Resources Vanguard
class huaRun implements Shop {
@Override
public void sell() {
System.out.println(
"Welcome to China Resources Vanguard...");
}
}
// Consumers
class Customer {
public void shopping(Shop shop) {
shop.sell();
}
}
@Test
public void fun() {
Customer customer
= new Customer();
customer.shopping(
new daRunFa());
customer.shopping(
new huaRun());
}
}

Final output: Welcome to RT-Mart...

Welcome to China Resources Vanguard...


6. Interface isolation Principles
The interface isolation principle suggests to establish a separate interface for each class instead of creating a large interface for all dependencies Its class to call.
Compared with the single responsibility and interface isolation principles, both improve the cohesion of the class and reduce the coupling of the code. But there are still differences between the two:
a) A single responsibility focuses on the division of class functions, while the principle of interface isolation is Pay attention to the isolation of interface dependencies to prevent the interface from being too bloated and causing unclear functions.
        b) 单一职责主要约束的是类,它针对的是程序中具体的实现和细节;而后者主要针对程序整体架构构建。
    
    注意点:接口尽量小,但是要有限度。对接口进行细化可以提高程序设计灵活性是不挣的事实,但是如果过小,则会造成接口数量过多,使设计复杂化。所以一定要适度。
    为依赖接口的类定制服务,只暴露给调用的类它需要的方法,它不需要的方法则隐藏起来。只有专注地为一个模块提供定制服务,才能建立最小的依赖关系。
提高内聚,减少对外交互。使接口用最少的方法去完成最多的事情。
    运用接口隔离原则,一定要适度,接口设计的过大或过小都不好。设计接口的时候,只有多花些时间去思考和筹划,才能准确地实践这一原则。 
 


 
7.迪米特法则
    迪米特法则强调,当两个软件实体无需直接通信,那么就不应该发生直接的相互调用,可以通过第三方转发该调用。迪米特法则不希望类之间建立直接的联系。如果真的有需要建立联系,也希望能通过它的友元类来转达。因此,应用迪米特法则有可能造成的一个后果就是:系统中存在大量的中介类,这些类之所以存在完全是为了传递类之间的相互调用关系——这在一定程度上增加了系统的复杂度。
     迪米特法则其根本思想,是强调了类之间的松耦合。类之间的耦合越弱,越有利于复用,一个处在弱耦合的类被修改,不会对有关系的类造成搏击,也就是说,信息的隐藏促进了软件的复用。
    实现迪米特法则的注意点:
        a) 类的成员权限尽量设置的低些
        b) 类的属性成员不要暴露,尽量设置get和set方法
        c) 只依赖该依赖的方法,只暴露该暴露的方法。

 

1.开闭原则(OCP–open close principle)是面向对象设计中“可复用设计”的基石。

 

    开闭原则中的“开”,指对组件功能的拓展是开放的,当需求发生变动时,能够对原模块进行拓展,使其满足新加进来的需求;

开闭原则中的“闭”,指对原功能代码的改动是封闭禁止的。

 

    因此,实现开闭原则的关键就在于使用“抽象”。把系统的全部可能的行为抽象为一个抽象的底层,这个抽象底层规定了全部详细实现必须提供的方法的特征。而作为系统设计的抽象层,要预见全部可能的拓展,当需要对系统的功能进行拓展时,可以从抽象底层导出一个或多个新的详细实现,能够改变系统的行为。

 

    假如一个系统符合开闭原则,那么它应该具有如下的优点:

1)可复用好。能够在软件开发完毕后,仍然能够对软件进行拓展,添加新的功能,而系统原先的代码不必变动。

2)可维护性好。对于系统原有的组件,不必进行改动,这样保证了原有功能的稳定性

 

 

2.单一原则

   

    就一个类而言,它只负责一项职责。这就要求在进行功能划分时,将其划分的更加清楚。

    单一职责原则是最简单但又最难运用的原则,需要设计人员发现类的不同职责并将其分离,再封装到不同的类或模块中。而发现类的多重职责需要设计人员具有较强的分析设计能力和相关重构经验。

    注意:单一职责同样也适用于方法。一个方法应该尽可能做好一件事情。如果一个方法处理的事情太多,其颗粒度会变得很粗,不利于重用。

 

 

3.里氏替换原则

    

    所有引用基类的地方必须能透明的使用其子类的对象—子类能够替换掉父类的对象,并且程序逻辑不变。

    里氏替换原则在以下两种场景有不同的表现:

        1)里氏替换原则是针对继承而言的,如果继承是为了更好的代码重用,即方法的共享,那么共享的父类方法就应该保持不变,子类不能重新定义。子类只能新添加方法来拓展功能,父类和子类都能实例化,子类继承的方法和父类是一致的,父类调用方法的地方,子类可以调用同一个继承来的方法,这时用子类方法替换父类方法,逻辑一致。

        2)而假如继承是为了实现多态,多态的前提是子类覆盖并重新定义父类的方法,为了符合里氏替换原则,这时应该将父类定义为抽象类,并定义抽象方法,让子类重新定义这些方法。当父类是抽象类时,也就不存在可实例化的父类对象在程序里。也就不存在子类能够替换父类方法这一场景了。

 

    因此,不符合里氏替换原则的场景是,子类和父类都是非抽象类,且父类的方法被子类重新定义了。如果通过重写父类的方法来完成新的功能,这样写起来虽然简单,但是整个继承体系的可复用性会比较差,特别是运用多态比较频繁时,程序运行出错的概率会非常大。如果程序违背了里氏替换原则,则继承类的对象在基类出现的地方会出现运行错误。这时其修正方法是:取消原来的继承关系,重新设计它们之间的关系。

    就拿下面这个例子来说,正方形在定义上属于长方形的一种,当把正方形设置为长方形的子类时,在一些情况下使用正方形的方法替换父类长方形的方法却会出错。

    /**

* 定义一个长方形类,只有标准的get和set方法
*
*
@author sxh
*
*/
public class Rectangle {
  
protected long width;
  
protected long height;

public void setWidth(long width) {
  
this.width = width;
}

public long getWidth() {
  
return this.width;
}

public void setHeight(long height) {
  
this.height = height;
}

public long getHeight() {
  
return this.height;
}}

    /**

* 定义一个长方形类,只有标准的get和set方法
*
*
@author sxh
*
*/
public class Rectangle {
  
protected long width;
  
protected long height;

public void setWidth(long width) {
  
this.width = width;
}

public long getWidth() {
  
return this.width;
}

public void setHeight(long height) {
  
this.height = height;
}

public long getHeight() {
  
return this.height;
}}

  /**

* 定义一个正方形类继承自长方形类,只有一个side
*
*
@author sxh
*
*/
public class Square extends Rectangle {
  
public void setWidth(long width) {
    
this.height = width;
    
this.width = width;
  }

  
public long getWidth() {
    
return width;
  }

  
public void setHeight(long height) {
    
this.height = height;
    
this.width = height;
  }

  
public long getHeight() {
    
return height;
  }
}

public class SmartTest{
  
/**
  * 长方形的长不短的增加直到超过宽
  *
@param r
  
*/
  public void resize(Rectangle r){
  
while (r.getHeight() <= r.getWidth() ){
    r.setHeight(r.getHeight()
+ 1);
  }
  }
}

  在上边的代码中我们定义了一个长方形和一个继承自长方形的正方形,看着是非常符合逻辑的,但是当我们调用SmartTest类中的resize方法时,长方形是可以的,但是正方形就会一直增大,一直long溢出。但是我们按照我们的里氏替换原则,父类可以的地方,换成子类一定也可以,所以上边的这个例子是不符合里氏替换原则的。

  /**

* 定义一个正方形类继承自长方形类,只有一个side
*
*
@author sxh
*
*/
public class Square extends Rectangle {
  
public void setWidth(long width) {
    
this.height = width;
    
this.width = width;
  }

  
public long getWidth() {
    
return width;
  }

  
public void setHeight(long height) {
    
this.height = height;
    
this.width = height;
  }

  
public long getHeight() {
    
return height;
  }
}

public class SmartTest{
  
/**
  * 长方形的长不短的增加直到超过宽
  *
@param r
  
*/
  public void resize(Rectangle r){
  
while (r.getHeight() <= r.getWidth() ){
    r.setHeight(r.getHeight()
+ 1);
  }
  }
}

  分析下来,我们发现子类并未重写或重载父类的方法,因此,这个继承关系根本不成立。这时,对这种不符合里氏替换原则的关系,我们需要重构他们的关系。

分享图片分享图片

我们重新定义一个父类,让正方形和长方形都继承这个父类。

public abstract class Quadrangle {

protected abstract long getWidth();
protected abstract long getHeight();
}

/**
* 自己声明height和width
*
@author sxh
*
*/
public class Rectangle extends Quadrangle {
  
private long width;
  
private long height;

  
public void setWidth(long width) {
    
this.width = width;
  }

  
public long getWidth() {
    
return this.width;
  }

  
public void setHeight(long height) {
    
this.height = height;
  }

  
public long getHeight() {
    
return this.height;
  }
}

public abstract class Quadrangle {

protected abstract long getWidth();
protected abstract long getHeight();
}

/**
* 自己声明height和width
*
@author sxh
*
*/
public class Rectangle extends Quadrangle {
  
private long width;
  
private long height;

  
public void setWidth(long width) {
    
this.width = width;
  }

  
public long getWidth() {
    
return this.width;
  }

  
public void setHeight(long height) {
    
this.height = height;
  }

  
public long getHeight() {
    
return this.height;
  }
}

/**

* 自己声明height和width
*
@author xingjiarong
*
*/
public class Square extends Quadrangle{
  
private long width;
  
private long height;

  
public void setWidth(long width) {
    
this.height = width;
    
this.width = width;
  }

  
public long getWidth() {
    
return width;
  }

  
public void setHeight(long height) {
    
this.height = height;
    
this.width = height;
  }

  
public long getHeight() {
    
return height;
  }
}

/**

* 自己声明height和width
*
@author xingjiarong
*
*/
public class Square extends Quadrangle{
  
private long width;
  
private long height;

  
public void setWidth(long width) {
    
this.height = width;
    
this.width = width;
  }

  
public long getWidth() {
    
return width;
  }

  
public void setHeight(long height) {
    
this.height = height;
    
this.width = height;
  }

  
public long getHeight() {
    
return height;
  }
}

在基类Quadrange类中没有赋值方法,因此类似于SamrtTest的resize()方法不可能适用于Quadrangle类型,而只能适用于不同的具体子类Rectangle和Aquare,因此里氏替换原则不可能被破坏了。

 


 

4.组合复用原则

    合成复用原则是通过将已有的对象纳入新对象中,作为新对象的成员对象来实现的,新对象可以调用已有对象的功能,从而达到复用。

    1)组合复用有以下好处:

新对象存取成分对象的唯一方法是通过成分对象的接口。

这种复用是黑箱复用,因为成分对象的内部细节是新对象所看不见的。

这种复用支持包装。

这种复用所需要的依赖较少。

每一个新的类可以将焦点集中到一个任务上。

这种复用可以在运行时间动态进行,新对象可以动态的引用与成分对象类型相同的对象。

组合复用的缺点就是用组合复用建造的系统会有较多的对象需要管理。

除了组合可以实现复用外,之前讲到的继承也是实现复用的一种手段。但继承会导致代码的耦合度增加,当基类发生变更时,它的子类要跟着相应的改变。按照组合复用的原则,应该首选组合,然后才是继承,并且在使用继承时要严格遵守里氏替换原则,必须满足“IS-a”的关系才是继承,而组合是“Has-a”的关系。

 


 

5.依赖倒置原则

    高层模块不应该依赖于低层模块,两者都应该依赖其抽象。抽象不应该依赖于细节,细节应该依赖于抽象。

    在软件的设计中,细节具有多变性,而抽象相对稳定,因此以抽象为基础搭建起来的系统比以实现为基础搭建起来机构更加稳定些。这里的抽象是指接口或者抽象类,而实现指具体的实现类。

    使用接口和抽象类的目的是制定好规范和契约,而不去设计任何具体的操作,把展示细节的任务交给实现类去完成。

    

public class Test1 {

// 商店
interface Shop {
void sell();
}
// 大润发超市
class daRunFa implements Shop{
@Override
public void sell() {
System.out.println(
"欢迎来到大润发...");
}
}
// 华润万家
class huaRun implements Shop {
@Override
public void sell() {
System.out.println(
"欢迎来到华润万家...");
}
}
// 消费者
class Customer {
public void shopping(Shop shop) {
shop.sell();
}
}
@Test
public void fun() {
Customer customer
= new Customer();
customer.shopping(
new daRunFa());
customer.shopping(
new huaRun());
}
}

public class Test1 {

// 商店
interface Shop {
void sell();
}
// 大润发超市
class daRunFa implements Shop{
@Override
public void sell() {
System.out.println(
"欢迎来到大润发...");
}
}
// 华润万家
class huaRun implements Shop {
@Override
public void sell() {
System.out.println(
"欢迎来到华润万家...");
}
}
// 消费者
class Customer {
public void shopping(Shop shop) {
shop.sell();
}
}
@Test
public void fun() {
Customer customer
= new Customer();
customer.shopping(
new daRunFa());
customer.shopping(
new huaRun());
}
}

最终输出:欢迎来到大润发...

欢迎来到华润万家...

最终输出:欢迎来到大润发...

欢迎来到华润万家...

 


 

6.接口隔离原则

    接口隔离原则建议为每一个类建立一个单独的接口,而不是建立一个很大的接口供所有依赖它的类去调用。

    对比单一职责和接口隔离原则,两者都提高了类的内聚性,降低了代码的耦合度。但两者仍有不同:

        a) 单一职责注重的是对类的功能的划分,接口隔离原则则是注重了对接口依赖的隔离,防止接口过于臃肿导致功能不明确。

        b) 单一职责主要约束的是类,它针对的是程序中具体的实现和细节;而后者主要针对程序整体架构构建。

    

    注意点:接口尽量小,但是要有限度。对接口进行细化可以提高程序设计灵活性是不挣的事实,但是如果过小,则会造成接口数量过多,使设计复杂化。所以一定要适度。

    为依赖接口的类定制服务,只暴露给调用的类它需要的方法,它不需要的方法则隐藏起来。只有专注地为一个模块提供定制服务,才能建立最小的依赖关系。

提高内聚,减少对外交互。使接口用最少的方法去完成最多的事情。

    运用接口隔离原则,一定要适度,接口设计的过大或过小都不好。设计接口的时候,只有多花些时间去思考和筹划,才能准确地实践这一原则。 

 


 

7.迪米特法则

    迪米特法则强调,当两个软件实体无需直接通信,那么就不应该发生直接的相互调用,可以通过第三方转发该调用。迪米特法则不希望类之间建立直接的联系。如果真的有需要建立联系,也希望能通过它的友元类来转达。因此,应用迪米特法则有可能造成的一个后果就是:系统中存在大量的中介类,这些类之所以存在完全是为了传递类之间的相互调用关系——这在一定程度上增加了系统的复杂度。

     迪米特法则其根本思想,是强调了类之间的松耦合。类之间的耦合越弱,越有利于复用,一个处在弱耦合的类被修改,不会对有关系的类造成搏击,也就是说,信息的隐藏促进了软件的复用。

    实现迪米特法则的注意点:

        a) 类的成员权限尽量设置的低些

        b) 类的属性成员不要暴露,尽量设置get和set方法

        c) 只依赖该依赖的方法,只暴露该暴露的方法。

Leave a Comment

Your email address will not be published.