Six Software Design Principles

Software Design Principles

       In the process of software development, in order to improve the maintainability and reusability of the software system and increase the scalability and flexibility of the software, programmers should try their best to develop programs according to the six principles, so as to improve the efficiency of software development and save software. Development costs and maintenance costs.

6 principles

       The six principles are: Open-Closed Principle , Liskov Substitution Principle , Dependency Inversion Principle , Interface Segregation Principle , Dimiter's Law , and Composition Reuse Principle . I will analyze, learn and understand one by one.

Open and close principle

       Open for extension, closed for modification.

       When the program needs to be expanded, the original code cannot be directly modified to achieve the effect of hot swapping (such as U disk, mouse, etc.), which can make the program more scalable and easy to maintain and upgrade.

       At this time we will use interfaces and abstract classes. The abstract class has good flexibility and wide adaptability. As long as the abstraction is reasonable, the stability of the software architecture can be basically guaranteed, and the details that often change in the software can be extended from the implementation class derived from the abstraction. When the software needs to change, only need Re-derived an implementation class to expand according to the requirements to complete the requirements.

       For example: Each hero in the king has its own skin. The skin is an integral part of each role. Users can choose their favorite skin to switch. Both the original skin and the companion skin have common characteristics, which can be defined An abstract class (AbstractSkin), and each concrete skin (DefaultSkin) and (CompanySkin) is a subclass of it. Players can choose skins according to their own preferences without modifying the source code, so it satisfies the principle of opening and closing.

Open and close principle
Part of the code is as follows:

	//抽象皮肤类
	public abstract class AbstractSkin {
    
    
		public abstract void display();
	}
	
	//默认皮肤类
	public class DefaultSkin extends AbstractSkin{
    
    
    	public void display(){
    
    
        	System.out.println("默认皮肤");
    	}
	}

	//伴生皮肤类
	public class CompanySkin extends AbstractSkin{
    
    
    	public void display(){
    
    
        	System.out.println("伴生皮肤");
    	}
	}

	//角色类
	public class role {
    
    
    	public AbstractSkin skin;

    	public void setSkin(AbstractSkin skin) {
    
    
       		this.skin = skin;
    	}
    	public void display(){
    
    
        	skin.display();
    	}
	}

	//测试类
	public class test {
    
    
    	public static void main(String[] args) {
    
    
        	//创建角色对象
        	role roles = new role();
        	//创建皮肤对象,这里可以切换不同的皮肤。
       		//DefaultSkin skin = new DefaultSkin();
        	CompanySkin skin = new CompanySkin();
        	//将皮肤设置给角色
        	roles.setSkin(skin);

        	//显示皮肤
        	roles.display();
    	}
	}

When the user changes the default skin, the system program outputs the default skin.
default skin
When replaced with a companion skin, the output is a companion skin.
companion skin
Summary : When the system wants to add a skin, it does not need to modify the original abstract class and other skin codes. It only needs to create a new class to inherit the AbstractSkin abstract class to add it, and to change the skin is to change the code on the client side. This is the open-closed principle, open for extension and closed for modification.

Liskov Substitution Principle

The Liskov substitution principle is one of the basic principles of object-oriented design.

Liskov Substitution Principle: Wherever a base class can appear, a subclass must appear. That is: subclasses can extend the functions of the parent class, but cannot change the original functions of the parent class. When the subclass inherits the parent class, in addition to adding new methods to complete the new functions, try not to rewrite the methods of the parent class.

If the new function is completed by rewriting the method of the parent class, although it is simple to write, the reusability of the overall inheritance system will be relatively poor, especially when polymorphism is frequently used, the probability of program running errors will be very high.

Example: A square is a rectangle and does not satisfy the Liskov substitution principle.

In mathematics, a square is a rectangle with equal length and width. Therefore, we can let the square inherit the rectangle when we write the program.

Liskov Substitution Principle
There are two member variables of length and width in the Rectangle class and their get and set methods.
In the Square class, override the two methods of setting the length and width of the parent class.
The resize method in RectangleDemo is to increase the width when the length of the rectangle is greater than the width, so that the width can be greater than the length. There is also a printLengthAndWidth() method that prints the length and width.

Part of the code is as follows:

public class Rectangle {
    
    
   //长方形类
    public double length;
    public double width;

    public double getLength() {
    
    
        return length;
    }

    public void setLength(double length) {
    
    
        this.length = length;
    }

    public double getWidth() {
    
    
        return width;
    }

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

//正方形类
public class Square extends Rectangle {
    
    
    public void setLength(double length){
    
    
        super.setLength(length);
        super.setWidth(length);
    }
    public void setWidth(double Width){
    
    
        super.setLength(Width);
        super.setWidth(Width);
    }
}

//测试类
public class RectangleDemo {
    
    

    public static void main(String[] args) {
    
    
        //创建长方形对象
        Rectangle r =new Rectangle();
        //设置长和宽
        r.setLength(50);
        r.setWidth(45);
        //调用resize方法进行扩宽
        resize(r);
        printLengthAndWidth(r);
    }
    //扩宽方法
    public static void resize(Rectangle rectangle){
    
    
        //判断宽如果比长小,进行扩宽操作
        while(rectangle.getWidth() <= rectangle.getLength()){
    
    
            rectangle.setWidth(rectangle.getWidth()+1);
        }
    }
    //打印长宽
    public static void printLengthAndWidth(Rectangle rectangle) {
    
    
        System.out.println(rectangle.getLength());
        System.out.println(rectangle.getWidth());
    }

}

The expected effect after running the code is consistent with the actual effect.
Liskov Substitution Principle
From a grammatical point of view, it is also true that I pass a Square to resize at this time.

		//创建正方形对象
        Square s=new Square();
        //设置长和宽
        s.setLength(50);
        //使用resize方法进行扩宽
        resize(s);
        printLengthAndWidth(s);

When we run the code, we can get
Liskov Substitution Principle
no output, and the program keeps running. This is because when we pass in a square, its length and width are always growing, because the square has the same length and width, only when the system generates an overflow error. , the program will stop. So, a normal rectangle is fine for this code, but a square is not.

**Conclusion:**In the resize method, the parameters of the Rectangle type cannot be replaced by the parameters of the Square type. If the replacement is performed, the expected effect will not be obtained, so the inheritance relationship between the Square and Rectangle classes violates According to the Liskov substitution principle, the inheritance relationship between them is not established.

Modification:
At this point we can redesign the relationship between them. Abstract a quadrilateral interface (Quadriliateral), let the Rectangle class and the Square class implement the Quadriliateral interface.

Improved class diagram:
Liskov Substitution Principle
Square and Rectangle are abstracted into a quadrilateral interface. The interface class contains two methods getLength() and getWidth(). The Square class implements the Quadrilateral interface, which contains side member variables and corresponding get and set methods and Two methods in the interface are overridden. There are two member variables of length and width and corresponding get and set methods in the Rectangle class. The RectangleDemo class includes the resize() widening method and the printLengthAndWidth(Quadrilateral quadrilateral) printing method.

Part of the code is as follows:

//Rectangle类
public class Rectangle implements Quadrilateral{
    
    
    public double length;
    public double width;

    public double getLength() {
    
    
        return length;
    }

    public void setLength(double length) {
    
    
        this.length = length;
    }

    public double getWidth() {
    
    
        return width;
    }

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

}

//Square类
public class Square implements Quadrilateral{
    
    
    public double side;

    public double getSide() {
    
    
        return side;
    }

    public void setSide(double side) {
    
    
        this.side = side;
    }

    public double getLength() {
    
    
        return side;
    }

    public double getWidth() {
    
    
        return side;
    }
}

//RectangleDemo类
public class RectangleDemo {
    
    
    public static void main(String[] args) {
    
    
        //创建长方形对象
        Rectangle r = new Rectangle();
        r.setLength(50);
        r.setWidth(45);
        //调用方法进行扩宽操作
        resize(r);

        printLengthAnhdWidth(r);
    }
    //扩宽方法
    public static void resize(Rectangle rectangle){
    
    
        while(rectangle.getWidth() <= rectangle.getLength()){
    
    
            rectangle.setWidth(rectangle.getWidth()+1);
        }
    }
    //打印长和宽
    public static void printLengthAnhdWidth(Quadrilateral quadrilateral) {
    
    
        System.out.println(quadrilateral.getLength());
        System.out.println(quadrilateral.getWidth());
    }
}

//interface
public interface Quadrilateral {
    
    
    //获取长
    double getLength();
    //获取宽
    double getWidth();
}

The result after running is:
Liskov Substitution Principle
At this time, objects of the Square class cannot use the resize method.

Summary: Subclasses can extend the functions of the parent class, but cannot change the original functions of the parent class. When the subclass inherits the parent class, in addition to adding new methods to complete the new functions, try not to rewrite the methods of the parent class.

Dependency Inversion Principle

The high-level module should not depend on the low-level module, both should depend on its abstraction; the abstraction should not depend on the details, the details should depend on the abstraction, that is, it is required to program the abstraction, not the implementation, which can reduce the coupling between the client and the implementation module.

Example: computer assembly

Assembling a computer requires accessories such as cpu, hard disk, memory stick, etc. Only when the accessories are available, the computer can run normally. There are intel, amd, etc. for the CPU, Seagate, Western Digital, etc. for the hard disk, and Kingston, Corsair, etc. for the memory stick.

Class Diagram:

Dependency Inversion Principle
Computer consists of XiJieHardDisk, IntelCpu, KingstonMemory. The Computer class contains three member variables and their corresponding get and set methods and a running method. The XiJieHardDisk class contains save data and get data get methods. The IntelCpu class contains the running method, and the KingstonMemory class contains the method of saving data.

Part of the code is as follows:

//硬盘类
public class XiJieHardDisk {
    
    

    //存储数据的方法
    public void save(String data){
    
    
        System.out.println("使用希捷硬盘存储数据为:" + data);
    }

    //获取数据的方法
    public String get(){
    
    
        System.out.println("使用希捷硬盘取数据");
        return "数据";
    }
}

//cpu类
public class IntelCpu {
    
    
    public void run(){
    
    
        System.out.println("使用Intel处理器");
    }
}

//内存条类
public class KingStonMemory {
    
    
    public void save(){
    
    
        System.out.println("使用金士顿内存条");
    }
}

//电脑类
public class Computer {
    
    

    public XiJieHardDisk hardDisk;
    public IntelCpu cpu;
    public KingStonMemory memory;

    public XiJieHardDisk getHardDisk() {
    
    
        return hardDisk;
    }

    public void setHardDisk(XiJieHardDisk hardDisk) {
    
    
        this.hardDisk = hardDisk;
    }

    public IntelCpu getCpu() {
    
    
        return cpu;
    }

    public void setCpu(IntelCpu cpu) {
    
    
        this.cpu = cpu;
    }

    public KingStonMemory getMemory() {
    
    
        return memory;
    }

    public void setMemory(KingStonMemory memory) {
    
    
        this.memory = memory;
    }

    public void run() {
    
    
        System.out.println("运行计算机");
        String data = hardDisk.get();
        System.out.println("从硬盘上获取的数据是:" + data);
        cpu.run();
        memory.save();
    }
}

//test类
public class test {
    
    
    public static void main(String[] args) {
    
    
        //创建组件对象
        XiJieHardDisk hardDisk = new XiJieHardDisk();
        IntelCpu cpu = new IntelCpu();
        KingStonMemory memory = new KingStonMemory();

        //创建计算机对象
        Computer c = new Computer();

        //组装计算机
        c.setCpu(cpu);
        c.setHardDisk(hardDisk);
        c.setMemory(memory);

        //运行计算机
        c.run();
    }
}

Code running results:
Dependency Inversion Principle
From the above, we can see that for this program, the brands of cpu, hard disk, and memory stick are fixed, and users cannot change them according to their own ideas. This violates the principle of opening and closing, so we will use this program Improve according to the principle of dependency inversion :

The improved class diagram is as follows: Dependency Inversion Principle
XiJieHardDisk, IntelCpu and KingstonMemory extract their parent interfaces, they implement the parent interface, and the computer does not use the specific implementation class when combining, but combines the abstract interface. If there are other brands later, we only need to create the implementation class of the brand.

Part of the code is as follows:

//cpu接口
public interface Cpu {
    
    
    //运行cpu
    public void run();
}

//内存条接口
public interface Memory {
    
    
    public void save();
}

//硬盘接口
public interface HardDisk {
    
    
    //存储数据
    public void save(String data);

    //获取数据
    public String get();
}

//根据上面的代码把之前的三个类去实现这三个接口,然后进行组装
public class Computer {
    
    
    public HardDisk hardDisk;
    public Cpu cpu;
    public Memory memory;

    public HardDisk getHardDisk() {
    
    
        return hardDisk;
    }

    public void setHardDisk(HardDisk hardDisk) {
    
    
        this.hardDisk = hardDisk;
    }

    public Cpu getCpu() {
    
    
        return cpu;
    }

    public void setCpu(Cpu cpu) {
    
    
        this.cpu = cpu;
    }

    public Memory getMemory() {
    
    
        return memory;
    }

    public void setMemory(Memory memory) {
    
    
        this.memory = memory;
    }
    //运行计算机
    public void run(){
    
    
        System.out.println("运行计算机");
        String data = hardDisk.get();
        System.out.println("从硬盘上获取的数据是:" + data);
        cpu.run();
        memory.save();
    }
}

//测试
public class test {
    
    
    public static void main(String[] args) {
    
    
        //创建计算机组件
        HardDisk hardDisk = new XiJieHardDisk();
        Cpu cpu = new IntelCpu();
        Memory memory = new KingStonMemory();

        //创建计算机对象
        Computer c = new Computer();

        //组装计算机
        c.setHardDisk(hardDisk);
        c.setCpu(cpu);
        c.setMemory(memory);

        //运行计算机
        c.run();
    }
}

The result after the program runs is: Dependency Inversion Principle
At this time, when the user needs to replace the cpu or hard disk, etc., he only needs to create an implementation class to implement the interface without modifying the original interface code, which conforms to the principle of opening and closing.

Summary: Program the abstraction, don't program the implementation.

Interface Segregation Principle

A client should not be forced to depend on methods it doesn't use; dependencies of one class on another should be based on the smallest possible interface.

For example: there are two methods in class A, and class B needs to use the b() method in class A. Interface Segregation Principle
It can be seen here that class B is forced to depend on the method c() that it does not use, and the dependence of a class on another class should be established on the smallest interface. At this point, we can declare the two methods in class A as two interfaces, and let class B implement the interface A, so as to satisfy the interface isolation principle. If class B needs the method c(), it can implement the interface C.
Interface Segregation Principle
Example: security door

Now create a security door of brand A, which has fireproof and waterproof functions. We can extract these two functions into one interface.
Interface Segregation Principle
Part of the code is as follows:

//安全门接口
public interface SafetyDoor {
    
    
    //防火
    void fireproof();
    //防水
    void waterproof();
}

//A类
public class ADoor implements SafetyDoor{
    
    

    public void fireproof() {
    
    
        System.out.println("防火");
    }


    public void waterproof() {
    
    
        System.out.println("防水");
    }
}

//test
public class test {
    
    
    public static void main(String[] args) {
    
    
        ADoor door = new ADoor();
        door.fireproof();
        door.waterproof();
    }
}

The result of the program is:
Interface Segregation Principle
if there is a Class B door that is only fireproof but not waterproof, we cannot directly implement the safety door interface, which will violate the interface isolation principle, so we improve it.

Improved class diagram:
Interface Segregation Principle
part of the code is as follows:

//防火
public interface fireproof {
    
    
    void fireproof();
}
//防水
public interface waterproof {
    
    
    void waterproof();
}

//A类门
public class ADoor implements fireproof,waterproof{
    
    

    public void fireproof() {
    
    
        System.out.println("防火");
    }
    public void waterproof() {
    
    
        System.out.println("防水");
    }
}

//B类门
public class BDoor implements fireproof{
    
    

    public void fireproof() {
    
    
        System.out.println("防火");
    }
}

//test
public class test {
    
    
    public static void main(String[] args) {
    
    
        //创建A类门对象
        ADoor door = new ADoor();
        //调用功能
        door.waterproof();
        door.fireproof();

        System.out.println("----------");
        //创建B门类对象
        BDoor door1 = new BDoor();
        door1.fireproof();
    }
}

The result of the program running is:
Interface Segregation Principle
Summary: The client should not be forced to depend on methods it does not use; the dependence of a class on another class should be established on the smallest interface. Some people may ask: Isn't there a lot of interfaces? There is no doubt, but it should be noted that java does not allow inheritance of multiple classes but can inherit multiple interfaces.

Law of Demeter

Demeter's law is also called the principle of least knowledge.

Only talk to your immediate friends , not strangers . (Talk only to your immediate friends and not to strangers).

Meaning: If two software entities do not need to communicate directly, then a direct mutual call should not occur, and the call can be forwarded through a third party. Its purpose is to reduce the degree of coupling between classes, improve the relative independence of modules, and protect private information.

The friends in Dimit's law refer to: the current object itself, the member objects of the current object, the objects created by the current object, the method parameters of the current object, etc. These objects are associated, aggregated or combined with the current object, and can be directly accessed methods of these objects.

For example: The star's daily affairs are handled by the agent, such as meeting fans and negotiating with media companies. Here, the star's friend is the agent, and the fans and the company are strangers, so it is suitable to use Dimit's law.

The class diagram is as follows:
Law of Demeter
The Star class contains a private name and methods for constructing and obtaining names with parameters. Fans and Company are the same as above. Agent contains 3 member variables and corresponding set methods, meeting methods, and business negotiation methods, which are aggregation relationships .

Part of the code is as follows:

//Star类
public class Star {
    
    
    private String name;

    public Star(String name) {
    
    
        this.name = name;
    }

    public String getName() {
    
    
        return name;
    }
}
//Fans类
public class Fans {
    
    
    private String name;

    public Fans(String name) {
    
    
        this.name = name;
    }

    public String getName() {
    
    
        return name;
    }
}
//Company类
public class Company {
    
    
    private String name;

    public Company(String name) {
    
    
        this.name = name;
    }

    public String getName() {
    
    
        return name;
    }
}
//Agent类
public class Agent {
    
    
    private Star star;
    private Fans fans;
    private Company company;

    public void setStar(Star star) {
    
    
        this.star = star;
    }

    public void setFans(Fans fans) {
    
    
        this.fans = fans;
    }

    public void setCompany(Company company) {
    
    
        this.company = company;
    }

    //和粉丝见面的方法
    public void meeting(){
    
    
        System.out.println(star.getName()+"和粉丝"+fans.getName()+"见面");
    }

    //和媒体公司洽谈的方法
    public void business(){
    
    
        System.out.println(star.getName()+"和"+company.getName()+"洽谈");
    }
}

//test类
public class test {
    
    
    public static void main(String[] args) {
    
    
        //创建经纪人对象
        Agent agent = new Agent();
        //创建明星对象
        Star star = new Star("ZCG");
        agent.setStar(star);
        //创建粉丝对象
        Fans fans = new Fans("张三");
        agent.setFans(fans);
        //创建公司对象
        Company company = new Company("学校");
        agent.setCompany(company);

        agent.meeting();//和粉丝见面
        agent.business();//和公司洽谈
    }
}

The results of the program are as follows:
Law of Demeter
Summary: From this example, we can see that Dimiter's Law is mainly to reduce the coupling between celebrities, fans and companies. The purpose of Dimit's law is to reduce the degree of coupling between classes, improve the relative independence of modules, and protect private information.

Synthetic Reuse Principles

The principle of composite reuse means: try to use association relationship such as combination or aggregation to realize it first, and then consider using inheritance relationship to realize it.

Generally, the reuse of classes is divided into two types: inheritance reuse and composition reuse.

Let's compare the pros and cons of the two:

Advantages of inheritance reuse:
simple and easy to implement.

shortcoming:

1. Inheritance reuse destroys the encapsulation of the class, because inheritance will expose the implementation details of the parent class to the subclass, and the parent class is transparent to the subclass, so this kind of reuse is also called "white box" reuse.

2. The coupling between the subclass and the parent class is high. Any change in the implementation of the parent class will lead to changes in the implementation of the subclass, which is not conducive to the extension and maintenance of the class.

3. It limits the flexibility of reuse. The implementation inherited from the parent class is static and defined at compile time, so it cannot be changed at runtime.

Combination or aggregation reuse can incorporate existing objects into new objects and make them part of new objects. New objects can call functions of existing objects. It has the following advantages:

1. It maintains the encapsulation of the class, because the internal details of the member objects are invisible to the new object, so this kind of reuse is also called "black box" reuse.

2. The coupling between objects is low, and abstraction can be declared at the member position of the class.

3. The flexibility of reuse is high. This reuse can be performed dynamically at runtime, and new objects can dynamically refer to objects of the same type as member objects.

Example: Classification of cars

Automobiles can be divided into gasoline vehicles, electric vehicles, solar vehicles, etc. according to the power source, and white, red, etc. according to the color. If both factors are considered for classification, there will be many combinations.

The class diagram is as follows: As
Synthetic Reuse Principles
can be seen from the above figure, many subclasses have been generated by inheritance reuse. If new energy or color (new class) needs to be defined, more subclasses will be generated, and we will reuse inheritance Modified to aggregate reuse.

The class diagram is as follows:

Synthetic Reuse Principles
After the improvement, when adding a light energy car, it is not necessary to add a subclass of the corresponding color.

Summary: Try to use association relationships such as combination or aggregation to achieve it first, and then consider using inheritance relationships to achieve it.

Speaking of the six software design principles here, all the explanations have been completed. In the next chapter, I will officially start learning the twenty-three design patterns. If you have any problems, you can point out and learn from each other.

Guess you like

Origin blog.csdn.net/weixin_46666816/article/details/123146006