Home > Java > JavaBase > body text

What are the six principles of design patterns?

青灯夜游
Release: 2023-02-22 11:32:06
Original
19047 people have browsed it

The six principles of design patterns: 1. The single responsibility principle, the core of which is to control the granularity of classes, decouple objects, and improve their cohesion; 2. The opening and closing principle, which can be achieved through "abstract constraints" , encapsulation changes" to achieve; 3. The Liskov substitution principle mainly explains some principles about inheritance; 4. The dependency inversion principle reduces the coupling between clients and implementation modules; 5. The interface isolation principle is to constrain the interface , Reduce the dependence of classes on interfaces; 6. Demeter's law requires limiting the width and depth of communication between software entities.

What are the six principles of design patterns?

The operating environment of this tutorial: windows7 system, java8 version, DELL G3 computer.

Regarding design patterns, I have read many design pattern books a long time ago, and I have read some of them several times. I have always hoped that I can use these design patterns when coding. However, in daily coding, the singleton is used most, followed by the observer and builder patterns (builder), which are used more frequently, and the others are rarely used.

The reason why they are not used is that they still cannot understand the idea of ​​design patterns and cannot connect these design patterns with the problems encountered in coding, so they cannot use design patterns.

In fact, the design patterns are proposed to solve a common problem. So when you think about which design pattern to adopt, you should first ask yourself what is the current problem? Choose the appropriate design pattern based on the problem.

After you become familiar with design patterns, you will find that there is an inclusive relationship between some design patterns, and they are even very similar, but different design patterns solve different problems.

When we design a module, we can consider it from the following perspectives:

  • What is the relationship between this module and other modules?

  • # Which parts of the module are unchanged, which parts are constantly changing, and how?

  • #What is the relationship between classes? Why do we need to rely on them? How can we not rely on them?

  • # Do you want to add an interface? What problem does the interface exist to solve?

Of course, this article does not teach you how to use design patterns. Rather, it explains the design principles of design patterns. Design patterns also follow some rules when they are designed.

The six principles of design patterns are as follows:

  • Single responsibility principle (classes, methods, interfaces)

  • Opening and closing principle (open for extension, closed for modification)

  • Rich substitution principle (relationship between base class and subclass )

  • Dependency inversion principle (rely on abstract interfaces, not concrete objects)

  • Interface isolation principle (interfaces are subdivided according to functions)

  • Dimiter's Law (closeness relationship between classes)

There are brackets next to each design principle to explain or describe the scope of application. Each principle is described in detail below.

Definition of single responsibility principle (class, method, interface)


Single Responsibility Principle (SRP) is also called the single function principle. The responsibility here refers to the reason for the change of the class. The single responsibility principle stipulates that a class should have and only one reason for its change, otherwise the class should be split (There should never be more than one reason for a class to change).

This principle states that objects should not assume too many responsibilities. If an object assumes too many responsibilities, there will be at least the following two disadvantages:

  • Changes in one responsibility may weaken or inhibit the ability of this class to implement other responsibilities;

  • When the client needs a certain responsibility of the object, it must not Not including all other unnecessary responsibilities leads to redundant code or wasted code.

Advantages of the Single Responsibility Principle

The core of the Single Responsibility Principle is to control the granularity of classes. Decouple objects and improve their cohesion. If you follow the single responsibility principle, you will have the following advantages.

  • Reduce the complexity of the class. If a class is responsible for only one responsibility, its logic is definitely much simpler than if it is responsible for multiple responsibilities.

  • #Improve the readability of the class. The complexity is reduced, and the readability is naturally improved.

  • 提高系统的可维护性。可读性提高,那自然更容易维护了。

  • 变更引起的风险降低。变更是必然的,如果单一职责原则遵守得好,当修改一个功能时,可以显著降低对其他功能的影响。

单一职责原则的实现方法

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

示例

public interface UserService {
    
    public void login(String username, String password);
    public void register(String email, String username, String password);
    public void logError(String msg);
    public void sendEmail(String email);
    
}
Copy after login
Copy after login

这段代码很显然存在很大的问题,UserService 既要负责用户的注册和登录,还要负责日志的记录和邮件的发送,并且后者的行为明显区别于前者。<br/>

假设我要修改发送邮件的逻辑就得修改这个类,这时候 qa 还得回归登录注册逻辑,这样明显不合理。

因此我们需要进行拆分,根据具体的职能可将其具体拆分如下:

UserService:只负责登录注册

public interface UserService {

    public void login(String username, String password);
    public void register(String email, String username, String password);

}
Copy after login

LogService :只负责日志<br/>

public interface LogService {
    public void logError(String msg);

}
Copy after login

EmailService: 只负责发送邮件

public interface EmailService {
    public void sendEmail(String email);

}
Copy after login

这时候,咱们再去回顾前面提到的优点,就能深深体会了。

这里只是讲了接口,其实对类也一样,甚至方法也是一样的。

对于类来说,根据类名,确保里面提供的方法都是属于这个类的。

对于方法,不要把不相关的对象实例作为参数传进来。如果你发现某个方法依赖某个不相关的对象,那么这个方法的实现可能就存在问题。

比如 android 中图片下载后显示到 imageView 中,我提供如下的方法:

loadImage(String url, ImageView view) {
// 下载图片,展示图片
}
Copy after login

对于 loadImage 这个方法,参数 url 是ok 的,但是参数 ImageView 却是不合理的。因为这里做了两个操作,下载图片,展示图片。应该将这个方法在进行拆分:

// 下载图片 
loadImage(String url) {

}
// 显示图片
displayImage(String url, ImageView view) {

// 调用 getBitmap (url)  获取图片
// 获取图片后将其设置到 view 中。

}

// 根据 url 获取图片, 
getBitmap(String url) {

}
Copy after login

这样整个逻辑就很清晰。后续需要修改下载逻辑,也不会影响到展示逻辑。当然其实还有个问题是,这两个方法要不要放在一个类里面?

开闭原则


开闭原则的实现方法:可以通过“抽象约束、封装变化”来实现开闭原则,即通过接口或者抽象类为软件实体定义一个相对稳定的抽象层,而将相同的可变因素封装在相同的具体实现类中。

因为抽象灵活性好,适应性广,只要抽象的合理,可以基本保持软件架构的稳定。而软件中易变的细节可以从抽象派生来的实现类来进行扩展,当软件需要发生变化时,只需要根据需求重新派生一个实现类来扩展就可以了。

示例

// 矩形
public class Rectangle {

    public double getWidth() {
        return width;
    }
    
    public double getHeight() {
        return height;
    }

}
Copy after login

需要计算矩形的面积

// 面积计算器
public class AreaCalculator {

    public double area(Rectangle shape){
        return shape.getWidth() * shape.getHeight();
    }
}
Copy after login

假设这时候,又多了一个圆形类

// 圆形
public class Circular {

    public double getRadius(){
        return radius;
    }
}
Copy after login

同样也需要计算他的面积,这时候就会变成下面这样子:

public class AreaCalculator {

    public double area(Object shape){
        if(shape instanceof Rectangle) {
            Rectangle rectangle = (Rectangle) shape;
            return rectangle.getWidth() * rectangle.getHeight();
        } else if (shape instanceof Circular) {
            Circular circular = (Circular) shape;
            return circular.getRadius() * circular.getRadius() * Math.PI;
        } else {
            throw new RuntimeException("There is no such type.");
        }
    }
}
Copy after login

这么更改完成,完全没有问题。但是在真实的生产环境中,情况更为复杂,更改涉及的部分较多,那样就可能导致牵一发动全身。并且,以前编写的经过测试的一些功能需要重新测试,甚至导致某些功能不可用。

改进版,把计算面积这个公有逻辑变成一个接口:

public interface Shape {

    public double getArea();

}
 
public class Rectangle implements Shape{

    public double getWidth() {
        return width;
    }

    public double getHeight() {
        return height;
    }

    public double getArea() {
        return getWidth() * getHeight();
    }
    
}
Copy after login

这样,当需求变更,需要计算圆形面积的时候,我们只需创建一个圆形的类,并实现 Shape 接口即可:<br/>

public class Circular implements Shape {

    public double getRadius(){
        return radius;
    }

    public double getArea() {
        return getRadius() * getRadius() * Math.PI;
    }
}
Copy after login

When calculating the area of ​​triangles and quadrilaterals..., we only need to let them implement the Shape interface without modifying the source code.

The Richter Substitution Principle


The Richter Substitution Principle mainly explains some principles about inheritance, that is, when should inheritance be used and what When should not use inheritance, and the principles underlying it. Liskov substitution is originally the basis for inheritance reuse. It reflects the relationship between base classes and subclasses, supplements the opening and closing principle, and regulates the specific steps to achieve abstraction.

The role of Liskov substitution principle

The main functions of Liskov substitution principle are as follows.

  • The Liskov substitution principle is one of the important ways to realize the opening and closing principle.

  • #It overcomes the shortcomings of poor reusability caused by overriding the parent class in inheritance.

  • #It is the guarantee of the correctness of the action. That is, the extension of the class will not introduce new errors into the existing system, reducing the possibility of code errors.

  • Enhance the robustness of the program, and achieve very good compatibility when changing, improve the maintainability and scalability of the program, and reduce the time required to change requirements. introduced risks.

How to implement the Liskov substitution principle (inheritance)

In layman terms, the Liskov substitution principle That is: subclasses can extend the functions of the parent class, but they cannot change the original functions of the parent class. In other words: when a subclass inherits a parent class, try not to override the parent class's methods except adding new methods to complete new functions.

Based on the above understanding, the definition of the Liskov substitution principle can be summarized as follows:

  • Subclasses can implement the abstract methods of the parent class, but cannot override the parent class. Non-abstract methods of the class

  • Subclasses can add their own unique methods

  • When a method of a subclass overrides a method of a parent class, the preconditions of the method (i.e., the input parameters of the method) are looser than the method of the parent class

  • When a method of a subclass implements a method of a parent class (overwriting/overloading or implementing an abstract method), the postconditions of the method (i.e. the output/return value of the method) are stricter or equal to those of the parent class

Although it is simple to write new functions by overriding the parent class method, the reusability of the entire inheritance system will be relatively poor, especially when polymorphic comparison is used. Frequently, the probability of program running errors will be very high.

If the program violates the Liskov substitution principle, the object of the inherited class will have a runtime error where the base class appears.

The correction method at this time is: cancel the original inheritance relationship and redesign the relationship between them.

Regarding the example of Liskov substitution principle, the most famous one is "a square is not a rectangle". Of course, there are many similar examples in life. For example, penguins, ostriches and kiwis are classified as birds from a biological perspective; but from a class inheritance relationship, because they cannot inherit the "bird" ability to fly function, so they cannot be defined as subclasses of "bird". Similarly, since "balloon fish" cannot swim, it cannot be defined as a subcategory of "fish"; "toy cannon" cannot blow up enemies, so it cannot be defined as a subcategory of "cannon", etc.

The best way for squares and rectangles is to add another parent class, and they inherit from this parent class at the same time.

Dependency Inversion (Wire Drawing Details)


The dependency inversion principle is one of the important ways to realize the opening and closing principle. It reduces the gap between customers and implementation modules. coupling between.

Because in software design, details are changeable, while the abstraction layer is relatively stable, so an architecture built on abstraction is much more stable than an architecture built on details. . The abstract here refers to the interface or abstract class, and the details refer to the specific implementation class.

The purpose of using interfaces or abstract classes is to formulate specifications and contracts without involving any specific operations, and leave the task of showing details to their implementation classes.

The functions of dependence and inversion principles

The main functions of the dependence inversion principle are as follows.

  • The dependency inversion principle can reduce the coupling between classes.

  • #The dependency inversion principle can improve the stability of the system.

  • #The dependency inversion principle can reduce the risks caused by parallel development.

  • #The dependency inversion principle can improve the readability and maintainability of code.

依赖倒置原则的实现方法

依赖倒置原则的目的是通过要面向接口的编程来降低类间的耦合性,所以我们在实际编程中只要遵循以下4点,就能在项目中满足这个规则。

  • 每个类尽量提供接口或抽象类,或者两者都具备。

  • 变量的声明类型尽量是接口或者是抽象类。

  • 任何类都不应该从具体类派生。

  • 使用继承时尽量遵循里氏替换原则。

依赖倒置原则在“顾客购物程序”中的应用。

分析:本程序反映了 “顾客类”与“商店类”的关系。商店类中有 sell() 方法,顾客类通过该方法购物以下代码定义了顾客类通过韶关网店 ShaoguanShop 购物

class Customer {
    public void shopping(ShaoguanShop shop) {
        //购物
        System.out.println(shop.sell());
    }
}
Copy after login

但是,这种设计存在缺点,如果该顾客想从另外一家商店(如婺源网店 WuyuanShop)购物,就要将该顾客的代码修改如下:

class Customer {
    public void shopping(WuyuanShop shop) {
        //购物
        System.out.println(shop.sell());
    }
}
Copy after login

顾客每更换一家商店,都要修改一次代码,这明显违背了开闭原则。

存在以上缺点的原因是:顾客类设计时同具体的商店类绑定了,这违背了依赖倒置原则。

解决方法是:定义“婺源网店”和“韶关网店”的共同接口 Shop,顾客类面向该接口编程,其代码修改如下:

class Customer {
    public void shopping(Shop shop) {
        //购物
        System.out.println(shop.sell());
    }
}

class Customer {
    public void shopping(Shop shop) {
        //购物
        System.out.println(shop.sell());
    }
}
Copy after login

这样,不管顾客类 Customer 访问什么商店,或者增加新的商店,都不需要修改原有代码了,其类如下图所示:


What are the six principles of design patterns?
程序代码如下:

package principle;
public class DIPtest
{
    public static void main(String[] args)
    {
        Customer wang=new Customer();
        System.out.println("顾客购买以下商品:"); 
        wang.shopping(new ShaoguanShop()); 
        wang.shopping(new WuyuanShop());
    }
}
//商店
interface Shop
{
    public String sell(); //卖
}
//韶关网店
class ShaoguanShop implements Shop
{
    public String sell()
    {
        return "韶关土特产:香菇、木耳……"; 
    } 
}
//婺源网店
class WuyuanShop implements Shop
{
    public String sell()
    {
        return "婺源土特产:绿茶、酒糟鱼……"; 
    }
} 
//顾客
class Customer
{
    public void shopping(Shop shop)
    {
        //购物
        System.out.println(shop.sell()); 
    }
}
Copy after login

程序的运行结果如下:

顾客购买以下商品:
韶关土特产:香菇、木耳……
婺源土特产:绿茶、酒糟鱼……
Copy after login

接口隔离原则(接口)


接口隔离原则(Interface Segregation Principle,ISP)要求程序员尽量将臃肿庞大的接口拆分成更小的和更具体的接口,让接口中只包含客户感兴趣的方法。

2002 年罗伯特·C.马丁给“接口隔离原则”的定义是:客户端不应该被迫依赖于它不使用的方法(Clients should not be forced to depend on methods they do not use)。该原则还有另外一个定义:一个类对另一个类的依赖应该建立在最小的接口上(The dependency of one class to another one should depend on the smallest possible interface)。

以上两个定义的含义是:要为各个类建立它们需要的专用接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。

接口隔离原则和单一职责都是为了提高类的内聚性、降低它们之间的耦合性,体现了封装的思想,但两者是不同的:

  • 单一职责原则注重的是职责,而接口隔离原则注重的是对接口依赖的隔离。

  • 单一职责原则主要是约束类,它针对的是程序中的实现和细节;接口隔离原则主要约束接口,主要针对抽象和程序整体框架的构建。

接口隔离原则的优点

接口隔离原则是为了约束接口、降低类对接口的依赖性,遵循接口隔离原则有以下 5 个优点。

  • 将臃肿庞大的接口分解为多个粒度小的接口,可以预防外来变更的扩散,提高系统的灵活性和可维护性。

  • 接口隔离提高了系统的内聚性,减少了对外交互,降低了系统的耦合性。

  • 如果接口的粒度大小定义合理,能够保证系统的稳定性;但是,如果定义过小,则会造成接口数量过多,使设计复杂化;如果定义太大,灵活性降低,无法提供定制服务,给整体项目带来无法预料的风险。

  • 使用多个专门的接口还能够体现对象的层次,因为可以通过接口的继承,实现对总接口的定义。

  • 能减少项目工程中的代码冗余。过大的大接口里面通常放置许多不用的方法,当实现这个接口的时候,被迫设计冗余的代码。

接口隔离原则的实现方法

在具体应用接口隔离原则时,应该根据以下几个规则来衡量。

  • 接口尽量小,但是要有限度。一个接口只服务于一个子模块或业务逻辑。

  • 为依赖接口的类定制服务。只提供调用者需要的方法,屏蔽不需要的方法。

  • 了解环境,拒绝盲从。每个项目或产品都有选定的环境因素,环境不同,接口拆分的标准就不同深入了解业务逻辑。

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

对于接口隔离,大家还是可以参考单一职责提到的示例:

public interface UserService {
    
    public void login(String username, String password);
    public void register(String email, String username, String password);
    public void logError(String msg);
    public void sendEmail(String email);
    
}
Copy after login
Copy after login

这时候,应该就能理解拆分的好处了。

迪米特法则 (类与类之间的关系)


迪米特法则(Law of Demeter,LoD)又叫作最少知识原则(Least Knowledge Principle,LKP),产生于 1987 年美国东北大学(Northeastern University)的一个名为迪米特(Demeter)的研究项目,由伊恩·荷兰(Ian Holland)提出,被 UML 创始者之一的布奇(Booch)普及,后来又因为在经典著作《程序员修炼之道》(The Pragmatic Programmer)提及而广为人知。

迪米特法则的定义是:只与你的直接朋友交谈,不跟“陌生人”说话(Talk only to your immediate friends and not to strangers)。其含义是:如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。

迪米特法则中的“朋友”是指:当前对象本身、当前对象的成员对象、当前对象所创建的对象、当前对象的方法参数等,这些对象同当前对象存在关联、聚合或组合关系,可以直接访问这些对象的方法。

迪米特法则的优点

迪米特法则要求限制软件实体之间通信的宽度和深度,正确使用迪米特法则将有以下两个优点。

  • 降低了类之间的耦合度,提高了模块的相对独立性。

  • 由于亲合度降低,从而提高了类的可复用率和系统的扩展性。

但是,过度使用迪米特法则会使系统产生大量的中介类,从而增加系统的复杂性,使模块之间的通信效率降低。所以,在釆用迪米特法则时需要反复权衡,确保高内聚和低耦合的同时,保证系统的结构清晰。

迪米特法则的实现方法

从迪米特法则的定义和特点可知,它强调以下两点:

  • 从依赖者的角度来说,只依赖应该依赖的对象。

  • 从被依赖者的角度说,只暴露应该暴露的方法。

所以,在运用迪米特法则时要注意以下 6 点。

  • 在类的划分上,应该创建弱耦合的类。类与类之间的耦合越弱,就越有利于实现可复用的目标。

  • 在类的结构设计上,尽量降低类成员的访问权限。

  • 在类的设计上,优先考虑将一个类设置成不变类。

  • 在对其他类的引用上,将引用其他对象的次数降到最低。

  • 不暴露类的属性成员,而应该提供相应的访问器(set 和 get 方法)。

  • 谨慎使用序列化(Serializable)功能

明星与经纪人的关系实例。

分析:明星由于全身心投入艺术,所以许多日常事务由经纪人负责处理,如与粉丝的见面会,与媒体公司的业务洽淡等。这里的经纪人是明星的朋友,而粉丝和媒体公司是陌生人,所以适合使用迪米特法则,其类图如下图所示。


What are the six principles of design patterns?
代码如下:

package principle;
public class LoDtest
{
    public static void main(String[] args)
    {
        Agent agent=new Agent();
        agent.setStar(new Star("林心如"));
        agent.setFans(new Fans("粉丝韩丞"));
        agent.setCompany(new Company("中国传媒有限公司"));
        agent.meeting();
        agent.business();
    }
}
//经纪人
class Agent
{
    private Star myStar;
    private Fans myFans;
    private Company myCompany;
    public void setStar(Star myStar)
    {
        this.myStar=myStar;
    }
    public void setFans(Fans myFans)
    {
        this.myFans=myFans;
    }
    public void setCompany(Company myCompany)
    {
        this.myCompany=myCompany;
    }
    public void meeting()
    {
        System.out.println(myFans.getName()+"与明星"+myStar.getName()+"见面了。");
    }
    public void business()
    {
        System.out.println(myCompany.getName()+"与明星"+myStar.getName()+"洽淡业务。");
    }
}
//明星
class Star
{
    private String name;
    Star(String name)
    {
        this.name=name;
    }
    public String getName()
    {
        return name;
    }
}
//粉丝
class Fans
{
    private String name;
    Fans(String name)
    {
        this.name=name;
    }
    public String getName()
    {
        return name;
    }
}
//媒体公司
class Company
{
    private String name;
    Company(String name)
    {
        this.name=name;
    }
    public String getName()
    {
        return name;
    }
}
Copy after login

程序的运行结果如下:

粉丝韩丞与明星林心如见面了。
中国传媒有限公司与明星林心如洽淡业务。
Copy after login

 到此,设计模式的六大原则就讲完了。

更多编程相关知识,请访问:编程教学!!

The above is the detailed content of What are the six principles of design patterns?. For more information, please follow other related articles on the PHP Chinese website!

Related labels:
source:php.cn
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template