zoukankan      html  css  js  c++  java
  • 设计模式(六大原则)

      我们编写软件的过程中常常面临着需求变更,每一次的变更其实都是对我们代码的可重用性、可读性、可维护性、可靠性的一次考验。设计模式就是为了让我们的代码具备这些功能,并使程序呈现高内聚,低耦合的特性。

    1、可重用性:相同的代码,不需要重复编写

    2、可读性:代码规范,命名规范,便于理解

    3、可维护性:新增功能容易,方便扩展

    4、可靠性:新增的功能,不会对历史功能造成影响

     

    一、单一职责原则

      单一职责原则(Single responsibility principle)规定每个类都应该有一个单一的功能,并且该功能应该由这个类完全封装起来。所有这个类的服务都应该严密的和该功能平行(功能平行,意味着没有依赖)。

    案例:交通工具运行的Demo

    package com.ycdhz.design;
    
    public class SinglePrinciple {
    
        public static void main(String[] args) {
    
            Vehicle vehicle = new Vehicle();
            vehicle.run("骑车");
            vehicle.run("飞机");
        }
    }
    
    class Vehicle {
    
        public void run(String vehicle){
            System.out.println(vehicle + "在路上跑");
        }
    }

     飞机是在天上飞的,Vehicle 的run 方法不能满足需求,也违反了单一职责原则。我可以根据不同的交通工具拆分成不同的类。

    package com.ycdhz.design;
    
    public class SinglePrinciple {
    
        public static void main(String[] args) {
    
            RoadVehicle roadVehicle = new RoadVehicle();
            roadVehicle.run("公交");
    
            AirVehicle airVehicle = new AirVehicle();
            airVehicle.run("飞机");
        }
    }
    
    class RoadVehicle {
    
        public void run(String vehicle){
            System.out.println(vehicle + "在路上跑");
        }
    }
    
    class AirVehicle {
    
        public void run(String vehicle){
            System.out.println(vehicle + "在天上飞");
        }
    }

     我们将Vehicle 类拆分为RoadVehicle、AirVehicle 满足了单一职责原则,但在实际的开发中我们往往不会这么做,我们做了类分解同时还修改了客户端,这样的改动太大。

    package com.ycdhz.design;
    
    public class SinglePrinciple {
    
        public static void main(String[] args) {
    
            Vehicle vehicle = new Vehicle();
            vehicle.roadRun("公交");
            vehicle.airRun("飞机");
        }
    }
    
    class Vehicle{
        public void roadRun(String vehicle){
            System.out.println(vehicle + "在路上跑");
        }
    
        public void airRun(String vehicle){
            System.out.println(vehicle + "在天上飞");
        }
    }

     我们没有对Vehicle 类进行拆分,而是新增了一个方法。虽然从类上来说不满足单一职责原则,但是从方法上来说依旧是满足的。

    总结

    1、降低类的复杂度,一个类只负责一项职责。

    2、提高类的可读性,可维护性。

    3、降低变更引起的风险。

    4、通常情况下我们应当遵守单一职责原则,只有逻辑足够简单,才可以在代码级违反单一职责原则;例如类中方法数量足够少,可以在方法级别保持单一职责原则。

     

    二、里氏替换原则

      里氏替换原则(Liskov Substitution Principle) 子类型必须可以替换掉它们的父类。子类可以扩展父类的功能,但不能改变父类原有的功能。简单来说就是一个软件实体如果使用的是一个父类的话,那么一定适用于其子类,如果把软件中父类替换为子类,程序的行为没有任何变化。

    案例:

    package com.ycdhz.design;
    
    public class LiskovPrinciple {
        
        public static void main(String[] args) {
            A a = new A();
            System.out.println("11-3=" + a.func1(11, 3));
    
            B b = new B();
            System.out.println("11-3=" + b.func1(11, 3));//这里本意是求出 11-3
            System.out.println("11+3+9=" + b.func2(11, 3));
        }
    }
    
    class A {
    
        public int func1(int num1, int num2) {
            return num1 - num2;
        }
    }
    
    class B extends A{
    
        @Override
        public int func1(int a, int b) {
            return a + b; // 重写了A的方法
        }
    
        public int func2(int a, int b) {
            return func1(a, b) + 9;
        }
    }

    我们发现原来运行正常的功能,因为类 B 无意中重写了父类的方法,造成功能出现错误。在实际开发过程中我们经常会通过重写来实现新功能,这样虽然开发简单,但是整个体系的复用性会比较差。我们可以定义一个更通俗的基类,让原有的父类,子类通过这个基类,去掉他们的继承关系。采用聚合、组合、依赖的方式来代替。

    package com.ycdhz.design;
    
    public class LiskovPrinciple {
    
        public static void main(String[] args) {
            B b = new B();
            System.out.println("11+3=" + b.func1(11, 3));
            System.out.println("11+3+9=" + b.func2(11, 3));
            System.out.println("11-3=" + b.func3(11, 3));
        }
    }
    
    class Base {
    
    }
    
    class A extends Base {
    
        public int func1(int num1, int num2) {
            return num1 - num2;
        }
    }
    
    class B extends Base {
    
        private A a = new A();
    
        public int func1(int num1, int num2) {
            return num1 + num2;
        }
    
        public int func2(int num1, int num2) {
            return func1(num1, num2) + 9;
        }
    
        public int func3(int num1, int num2) {
            return a.func1(num1, num2);
        }
    }

    总结:

    1、里氏替换原则 所有引用基类的地方必须能透明地使用其子类的对象。
    2、在使用继承时,遵循里氏替换原则,在子类中尽量不要重写父类的方法。
    3、里氏替换原则告诉我们,继承实际上让两个类耦合性增强了,在适当的情况下,可以通过 聚合、组合、依赖来解决问题。

    三、依赖倒置原则

      依赖倒转原则(Dependence Inversion Principle) 需要满足两点:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。相对于细节(实现类)的多变性,抽象(接口/抽象类)的东西要稳定的多。以抽象为基础搭建的架构比以细节为基础的架构要稳定的多。

    案例: 用户接收Email消息功能

    package com.ycdhz.design;
    
    public class DependecyPrinciple {
    
        public static void main(String[] args) {
            Persion persion = new Persion();
            persion.Recive(new Email());
        }
    }
    
    class Email {
        public void getInfo() {
            System.out.println("Email Msg!");
        }
    }
    
    class Persion {
        public void Recive(Email email){
            email.getInfo();
        }
    }

    如果这个时候需求发生了变更,我们需要接收微信、短信、QQ等等,则我们需要为每一个功能新增一个类,同时Perons 也要增加相应的接收方法。这显然不符合我们的依赖倒转原则,这时我们只引入一个IRecive 接口,让Perons 与IRecive 接口发生依赖。

    package com.ycdhz.design;
    
    public class DependecyPrinciple {
    
        public static void main(String[] args) {
            Persion persion = new Persion();
            persion.Recive(new Email());
        }
    }
    
    class Persion {
        public void Recive(IRecive recive){
            recive.getInfo();
        }
    }
    
    interface IRecive{
        void getInfo();
    }
    
    class Email implements IRecive {
        @Override
        public void getInfo() {
            System.out.println("Email Msg!");
        }
    }

    这个时候我们只需要根据需求添加具体的业务代码,而不需要改变客户端。

    依赖关系的三种传递方式

    1、接口传递
    2、构造方法传递
    3、setter 方式传递

    总结:

    1、低层模块尽量都要有抽象类或接口,或者两者都有,程序稳定性更好。
    2、变量的声明类型尽量是抽象类或接口, 这样我们的变量引用和实际对象间,就存在一个缓冲层,利于程序扩展和优化。
    3、继承时遵循里氏替换原则。

    四、接口隔离原则

      接口隔离原则(Interface Segregation Principle) 客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。

    案例

    如图现在 ClientA 需要通过接口 Interface1 接口依赖ServiceA 中的Operation1、Operation2、Operation3 三个方法,ClientB 需要通过接口 Interface1 依赖ServiceB中的Operation1、Operation4、Operation5 三个方法。

    package com.ycdhz.design;
    
    public class InterfacePrinciple {
    
        public static void main(String[] args) {
            ClientA a = new ClientA();
            a.operation1(new ServiceA());
            a.operation2(new ServiceA());
            
            ClientB b = new ClientB();
            b.operation1(new ServiceB());
            b.operation4(new ServiceB());
        }
    }
    
    interface Interface1{
        void operation1();
        void operation2();
        void operation3();
        void operation4();
        void operation5();
    }
    
    
    class ServiceA implements Interface1{
    
        @Override
        public void operation1() {
            System.out.println("Class ServiceA operation 1");
        }
    
        @Override
        public void operation2() {
            System.out.println("Class ServiceA operation 2");
        }
    
        @Override
        public void operation3() {
            System.out.println("Class ServiceA operation 3");
        }
    
        @Override
        public void operation4() {
            System.out.println("Class ServiceA operation 4");
        }
    
        @Override
        public void operation5() {
            System.out.println("Class ServiceA operation 5");
        }
    }
    
    class ServiceB implements Interface1{
    
        @Override
        public void operation1() {
            System.out.println("Class ServiceB operation 1");
        }
    
        @Override
        public void operation2() {
            System.out.println("Class ServiceB operation 2");
        }
    
        @Override
        public void operation3() {
            System.out.println("Class ServiceB operation 3");
        }
    
        @Override
        public void operation4() {
            System.out.println("Class ServiceB operation 4");
        }
    
        @Override
        public void operation5() {
            System.out.println("Class ServiceB operation 5");
        }
    }
    
    class ClientA {
        public void operation1(Interface1 interface1){
            interface1.operation1();
        }
    
        public void operation2(Interface1 interface1){
            interface1.operation2();
        }
    
        public void operation3(Interface1 interface1){
            interface1.operation3();
        }
    }
    
    class ClientB {
        public void operation1(Interface1 interface1){
            interface1.operation1();
        }
    
        public void operation4(Interface1 interface1){
            interface1.operation4();
        }
    
        public void operation5(Interface1 interface1){
            interface1.operation5();
        }
    }

    我们发现Interface1 接口对于ServiceA、ServiceB 来说都不是最小接口,明显违背了接口隔离原则。我可以根据不同的需要对接口进行拆分,将接口 Interface1 拆分为独立的几个接口( Interface1、Interface2、Interface3),类 ServiceA 和类ServiceB 分别与他们需要的接口建立依赖关系。

    package com.ycdhz.design;
    
    public class InterfacePrinciple {
    
        public static void main(String[] args) {
            ClientA a = new ClientA();
            a.operation1(new ServiceA());
            a.operation2(new ServiceA());
            a.operation3(new ServiceA());
            
            ClientB b = new ClientB();
            b.operation1(new ServiceB());
            b.operation4(new ServiceB());
            b.operation5(new ServiceB());
        }
    }
    
    interface Interface1{
        void operation1();
    }
    
    interface Interface2{
        void operation2();
        void operation3();
    }
    
    interface Interface3{
        void operation4();
        void operation5();
    }
    
    class ServiceA implements Interface1, Interface2{
    
        @Override
        public void operation1() {
            System.out.println("Class ServiceA operation 1");
        }
    
        @Override
        public void operation2() {
            System.out.println("Class ServiceA operation 2");
        }
    
        @Override
        public void operation3() {
            System.out.println("Class ServiceA operation 3");
        }
    }
    
    class ServiceB implements Interface1, Interface3 {
    
        @Override
        public void operation1() {
            System.out.println("Class ServiceB operation 1");
        }
    
        @Override
        public void operation4() {
            System.out.println("Class ServiceB operation 4");
        }
    
        @Override
        public void operation5() {
            System.out.println("Class ServiceB operation 5");
        }
    }
    
    class ClientA {
        public void operation1(Interface1 interface1){
            interface1.operation1();
        }
    
        public void operation2(Interface2 interface1){
            interface1.operation2();
        }
    
        public void operation3(Interface2 interface1){
            interface1.operation3();
        }
    }
    
    class ClientB {
        public void operation1(Interface1 interface1){
            interface1.operation1();
        }
    
        public void operation4(Interface3 interface1){
            interface1.operation4();
        }
    
        public void operation5(Interface3 interface1){
            interface1.operation5();
        }
    }

    总结

    1、将接口 Interface1 进行合理的拆分,让类ClientA 和类ClientB 分别与他们需要的接口建立依赖关系,这样就满足了接口隔离原则。

     

    五、迪米特法则

      迪米特法则(Demeter Principle)又叫 最少知道原则,即一个类 对自己依赖的类知道的越少越好。也就是说,对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内部。对外除了提供的 public 方法,不对外泄露任何信息。

      迪米特法则还有个更简单的定义:只与直接的朋友通信。每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。耦合的方式很多依赖、关联、组合、聚合等。其中我们称出现成员变量、方法参数、方法返回值中的类为直接的朋友,而出现在局部变量中的类不是直接的朋友。

    package com.ycdhz.design;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class DemeterPrinciple {
    
        public static void main(String[] args) {
            CompanyManager manager = new CompanyManager();
            manager.printAllEmployee(new SubCompanyManager());
        }
    }
    
    //总公司员工
    class Employee {
        private String id;
    
        public void setId(String id) {
            this.id = id;
        }
    
        public String getId() {
            return id;
        }
    }
    
    //分公司员工
    class SubEmployee {
        private String id;
    
        public void setId(String id) {
            this.id = id;
        }
    
        public String getId() {
            return id;
        }
    }
    
    class SubCompanyManager {
        public List<SubEmployee> getAllEmployee() {
            List<SubEmployee> list = new ArrayList<SubEmployee>();
            for (int i = 0; i < 100; i++) {
                SubEmployee emp = new SubEmployee();
                //为分公司人员按顺序分配一个ID
                emp.setId("分公司" + i);
                list.add(emp);
            }
            return list;
        }
    }
    
    class CompanyManager {
        public List<Employee> getAllEmployee() {
            List<Employee> list = new ArrayList<Employee>();
            for (int i = 0; i < 30; i++) {
                Employee emp = new Employee();
                //为总公司人员按顺序分配一个ID
                emp.setId("总公司" + i);
                list.add(emp);
            }
            return list;
        }
    
        public void printAllEmployee(SubCompanyManager sub) {
            List<SubEmployee> list1 = sub.getAllEmployee();
            for(SubEmployee e:list1){
                System.out.println(e.getId());
            }
    
            List<Employee> list = this.getAllEmployee();
            for (Employee e : list) {
                System.out.println(e.getId());
            }
        }
    }

    我们发现在CompanyManager 类的printAllEmployee 方法中与子公司的员工发生了耦合,按照迪米特法则只与直接的朋友发生通信,而SubEmployee 类并不是CompanyManager 类的直接朋友(以局部变量出现的耦合不属于直接朋友),从逻辑上讲总公司只与他的分公司耦合就行了,与分公司的员工并没有任何联系,这样设计显然是增加了不必要的耦合。

    class SubCompanyManager {
        public List<SubEmployee> getAllEmployee() {
            List<SubEmployee> list = new ArrayList<SubEmployee>();
            for (int i = 0; i < 100; i++) {
                SubEmployee emp = new SubEmployee();
                //为分公司人员按顺序分配一个ID
                emp.setId("分公司" + i);
                list.add(emp);
            }
            return list;
        }
    
        public void printEmployee() {
            List<SubEmployee> list = this.getAllEmployee();
            for (SubEmployee e : list) {
                System.out.println(e.getId());
            }
        }
    }
    
    class CompanyManager {
        public List<Employee> getAllEmployee() {
            List<Employee> list = new ArrayList<Employee>();
            for (int i = 0; i < 30; i++) {
                Employee emp = new Employee();
                //为总公司人员按顺序分配一个ID
                emp.setId("总公司" + i);
                list.add(emp);
            }
            return list;
        }
    
        public void printAllEmployee(SubCompanyManager sub) {
            sub.printEmployee();
    
            List<Employee> list = this.getAllEmployee();
            for (Employee e : list) {
                System.out.println(e.getId());
            }
        }
    }

     修改后,为分公司增加了打印人员ID的方法,总公司直接调用来打印,从而避免了与分公司的员工发生耦合。

    总结:

    1、迪米特法则的核心是降低类之间的耦合。
    2、由于每个类都减少了不必要的依赖,因此迪米特法则只是要求降低类间(对象间)耦合关系, 并不是要求完全没有依赖关系。

    六、开闭原则

    开闭原则(Open Closed Principle)是编程中最基础、最重要的设计原则。软件实体(类,模块,函数)应该可以扩展,但是不可以修改。即对于扩展是开放的,对于更改是封闭的。面对需求,对程序的改动是通过增加新代码进行的,而不是更改现有的代码。开放-封闭原则是面向对象设计的核心所在,遵循这个原则可以使系统可维护,可扩展,可复用,灵活性好。

    package com.ycdhz.design;
    
    public class OpenClosedPrinciple {
    
        public static void main(String[] args) {
            GraphicEditor editor = new GraphicEditor();
            editor.drawCircle(new Circle());
            editor.drawRectangle(new Rectangle());
        }
    }
    
    //Shape 类,基类
    class Shape {
        int m_type;
    }
    
    class Rectangle extends Shape {
        Rectangle() {
            super.m_type = 1;
        }
    }
    class Circle extends Shape {
        Circle() {
            super.m_type = 2;
        }
    }
    
    class GraphicEditor {
        public void drawShape(Shape s) {
            if (s.m_type == 1) {
                drawRectangle(s);
            } else if (s.m_type == 2){
                drawCircle(s);
            }
        }
    
        public void drawRectangle(Shape r) {
            System.out.println("绘制矩形");
        }
    
        public void drawCircle(Shape r) {
            System.out.println("绘制圆形");
        }
    }

    这个时候如果我们需要绘制一个三角形,我们发现需要做很多的修改,违背了开闭原则。其实我们可以将Shape 类做成抽象类,并提供一个抽象的 draw 方法,让子类去实现,这样我们有新的图形种类时,只需要让新的图形类继承 Shape,并实现 draw 方法即可。

    package com.ycdhz.design;
    
    public class OpenClosedPrinciple {
    
        public static void main(String[] args) {
            GraphicEditor editor = new GraphicEditor();
            editor.drawShape(new Circle());
            editor.drawShape(new Rectangle());
        }
    }
    
    abstract class Shape{
        public abstract void draw();
    }
    
    class Rectangle extends Shape {
    
        @Override
        public void draw() {
            System.out.println("绘制矩形");
        }
    }
    class Circle extends Shape {
    
        @Override
        public void draw() {
            System.out.println("绘制圆形");
        }
    }
    
    class GraphicEditor{
        public void drawShape(Shape s) {
            s.draw();
        }
    }

    总结

    1、开闭原则(Open Closed Principle)是编程中 最基础、最重要的设计原则。
    2、一个软件实体如类,模块和函数应该 对扩展开放( 对提供方),对 修改关闭( 对使用方)。用抽象构建框架,用实现扩展细节。因为抽象灵活性好,适应性广,只要抽象的合理,可以基本保持软件架构的稳定。
    3、当软件需要变化时,尽量 通过扩展软件实体的行为来实现变化,而不是 通过修改已有的代码来实现变化。

     

     

     

  • 相关阅读:
    利用正则表达式限制网页表单里的文本框输入内容小结
    实现注册页面中的倒计时功能代码
    asp.net中Response.Write用法小结
    数据库连接字符串
    asp.net中页面延时跳转代码
    C#网络编程socket使用总结
    CSS选择器总结
    C#面向对象三大特性总结
    HTML总结
    ASP.NET页面生命周期
  • 原文地址:https://www.cnblogs.com/jiangyaxiong1990/p/13444766.html
Copyright © 2011-2022 走看看