zoukankan      html  css  js  c++  java
  • 设计模式-原则

    七大设计原则

    所有原则都为了降低类之间的耦合。

    一、单一职责

    • 降低类复杂度,一个类负责一项职责。
    • 提高类可读性,可维护性。
    • 降低变更引起的风险。
    • 通常情况下,应遵循。只有逻辑够简单,才可在代码级别(通过if...else...)违反;只有类方法数量足够少,才可在方法级别(定义不同方法实现不同操作)保持单一职责

    二、接口隔离

    客户端不应依赖不需要的接口,即对一个类的依赖,应该建立在最小接口上。

    如:接口Interface有1、2、3等方法,B类实现了Interface接口。A类通过接口依赖B类,但A只需要用到接口中1、2两个方法。
    按接口隔离原则应把接口分离:
    1、Interface分成两个接口:Interface1有方法1、2;Interface2有方法3;
    2、B类实现接口Interface1即可;
    3、如果后续某个类需要用到Interface中1、2、3方法,再让B实现Interface2。
    

    三、依赖倒转

    • 底层模块尽量使用抽象类或接口,或两者都有,程序稳定性更好
    • 变量声明类型尽量用抽象类或接口。这样变量引用和实际对象存在一个缓冲层(即接口),利用程序扩展和优化。
    • 继承时遵循里氏替换原则
    //客户使用端
    public class Client {
       public static void main(String[] args){
           OpenAndCloseTv openAndCloseTv = new OpenAndCloseTv();
           openAndCloseTv.open(new Changhong());
           openAndCloseTv.open(new Kangjia());
       }
    }
    
    //开关接口
    interface IOpenAndCloseTv{
        public void open(ITV tv);
    }
    //实现
    class OpenAndCloseTv implements IOpenAndCloseTv{
        @Override
        public void open(ITV tv) {
            tv.play();
        }
    }
    
    //tv类型接口
    interface ITV{
        public void play();
    }
    //定义不同tv类型
    class Changhong implements ITV{
        @Override
        public void play() {
            System.out.println("长虹电视打开。。。");
        }
    }
    class Kangjia implements ITV{
        @Override
        public void play() {
            System.out.println("康佳电视打开了。。。");
        }
    }
    
    结果:
    长虹电视打开。。。
    康佳电视打开了。。。
    

    依赖关系传递三种方式:

    • 接口传递
    //开关接口
    interface IOpenAndCloseTv{
        public void open(ITV tv);
    }
    //实现
    class OpenAndCloseTv implements IOpenAndCloseTv{
        @Override
        public void open(ITV tv) {
            tv.play();
        }
    }
    ---------------------使用
    public static void main(String[] args){
        OpenAndCloseTv openAndCloseTv = new OpenAndCloseTv();
        openAndCloseTv.open(new Changhong());
        openAndCloseTv.open(new Kangjia());
    }
    
    • 构造方法传递
    //开关接口
    interface IOpenAndCloseTv{
        public void open();
    }
    //实现
    class OpenAndCloseTv implements IOpenAndCloseTv{
        private ITV tv; // 成员变量
        public OpenAndCloseTv(ITV tv){
            this.tv = tv;
        }
        @Override
        public void open() {
            tv.play();
        }
    }
    ---------------------使用
    public static void main(String[] args){
        OpenAndCloseTv openAndCloseTv = new OpenAndCloseTv(new Changhong());
        openAndCloseTv.open();
        OpenAndCloseTv openAndCloseTv2 = new OpenAndCloseTv(new Kangjia());
        openAndCloseTv2.open();
    }
    
    • setter方法传递
    //开关接口
    interface IOpenAndCloseTv{
        public void open();
        public void setTv(ITV tv)
    }
    //实现
    class OpenAndCloseTv implements IOpenAndCloseTv{
        private ITV tv; // 成员变量
        
        @Override    
        public void setTv(ITV tv){
            this.tv = tv;
        }
        @Override
        public void open() {
            tv.play();
        }
    }
    ---------------------使用
    public static void main(String[] args){
        OpenAndCloseTv openAndCloseTv = new OpenAndCloseTv();
        openAndCloseTv.setTv(new Changhong();
        openAndCloseTv.open();
    }
    

    四、里氏替换

    一个类被其他类所继承,该类修改时,必须考虑所有子类。父类修改后,所有子类有可能产生故障。
    如何正确使用继承?--》里氏替换原则

    • 使用基类的地方必须能透明使用子类对象。
    • 使用继承时,在子类中尽量不要重写父类方法。
    • 继承实际让两个类耦合性增强,在适当情况下可通过聚合,组合,依赖解决问题,不要通过继承。

    如某类子类需要改写父类方法,应把子类提升和父类一样的档次,把共同方法抽取,让子类和父类继承同一个更基层的基类。

    若要改变父,就不要做父子关系了,做兄弟关系吧。

    public class Liskov {
        public static void main(String[] args){
    
            A a = new A();
            System.out.println("11-3="+a.func1(11,3));
            System.out.println("1-8="+a.func1(1,8));
    
            System.out.println("----------------------------");
            B b = new B();
            System.out.println("11-3="+b.func1(11,3)); // 这里其实已经覆写父类方法了,所以-的话是错误的。
            System.out.println("1-8="+b.func1(1,8));
            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{
        // 重新了A 类方法,可能是无意识
        @Override
        public int func1(int a,int b){
            return a + b;
        }
        public int func2(int a,int b){
            return func1(a,b)+9;
        }
    }
    

    通过里氏替换原则:

    public class Liskov2 {
        public static void main(String[] args){
    
            A a = new A();
            System.out.println("11-3="+ a.func1(11,3));
            System.out.println("1-8="+ a.func1(1,8));
    
            System.out.println("----------------------------");
            B b = new B();
            System.out.println("11+3="+ b.func1(11,3)); // 这里其实已经覆写父类方法了,所以-的话是错误的。
            System.out.println("1+8="+ b.func1(1,8));
            System.out.println("11+3+9="+ b.func2(11,3));
    
            System.out.println("11-3="+ b.func3(11,3)); //实际调用的是A类的方法。
        }
    }
    class base{
        //共有的更基础的方法、成员变量定义到该类
        //如:
        public int funcMult(int num1,int num2){
            return num1*num2;
        }
    }
    class A extends base{
        public int func1(int num1,int num2){
            return num1 - num2;
        }
    }
    class B extends base{
        private A  aobj = new A(); //通过组合方式解决继承问题-step1
    
        public int func1(int a,int b){
            return a + b;
        }
        public int func2(int a,int b){
            return func1(a,b)+9;
        }
        public int func3(int a,int b){
            return aobj.func1(a,b); //通过组合方式解决继承问题-step2
        }
    }
    
    ----------结果
    11-3=8
    1-8=-7
    ----------------------------
    11+3=14
    1+8=9
    11+3+9=23
    11-3=8
    

    五、开闭原则

    • 最基础、最重要的设计原则
    • 一个实体如类、模块、函数应【扩展开发(对提供方),修改关闭(对使用方)】。抽象构建框架,实现扩展细节。
    • 软件需变化,尽量通过扩展代码而不是修改已有代码。
    • 编程中遵循其他原则,及设计模式目的就是遵循开闭原则(核心)。

    不遵循OCP原则:

    public class Ocp {
        public static void main(String[] args){
            GraphicEditor graphicEditor = new GraphicEditor();
            graphicEditor.drawShape(new Rectangle());
            graphicEditor.drawShape(new Circle());
            graphicEditor.drawShape(new Triangle());
        }
    }
    
    //这是一个用于绘图的类 [使用方]
    class GraphicEditor {
        //接收Shape对象,调用draw方法
        public void drawShape(Shape s) {
            if (s.m_type == 1){
                drawRectangle();
            }else if (s.m_type == 2){
                drawCircle();
            }else if (s.m_type == 3){ //新增图形,使用方需要进行修改
                drawTriangle();
            }
        }
        public void drawRectangle(){ System.out.println(" 绘制矩形 "); }
        public void drawCircle() { System.out.println(" 绘制圆形 "); }
        //新增图形,使用方需要进行修改
        public void drawTriangle() { System.out.println(" 绘制三角形 "); }
    }
    
    //定义各种图形
    //Shape类,基类,
    abstract class Shape {
        int m_type;
    }
    //矩形
    class Rectangle extends Shape {
        Rectangle() { super.m_type = 1; }
    }
    //圆形
    class Circle extends Shape {
        Circle() { super.m_type = 2; }
    }
    //新增三角形
    class Triangle extends Shape {
        Triangle() { super.m_type = 3; }
    }
    
    ----------结果
     绘制矩形 
     绘制圆形 
     绘制三角形 
    

    遵循OCP原则:

    public class Ocp {
        public static void main(String[] args) {
            //使用看看存在的问题
            GraphicEditor graphicEditor = new GraphicEditor();
            graphicEditor.drawShape(new Rectangle());
            graphicEditor.drawShape(new Circle());
            graphicEditor.drawShape(new Triangle());
            graphicEditor.drawShape(new OtherGraphic());
        }
    }
    
    //这是一个用于绘图的类 [使用方]
    class GraphicEditor {
        //接收Shape对象,调用draw方法
        public void drawShape(Shape s) { s.draw(); //只需要新增图形就可以,不需要修改代码。 }
    }
    
    //Shape类,遵守ocp原则通过创建一个基类,
    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 Triangle extends Shape {
        @Override
        public void draw() { System.out.println(" 绘制三角形 "); }
    }
    //新增一个图形
    class OtherGraphic extends Shape {
        @Override
        public void draw() { System.out.println(" 绘制其它图形 "); }
    }
    
    ----------结果
     绘制矩形 
     绘制圆形 
     绘制三角形 
     绘制其它图形 
    

    六、迪米特法则

    核心是为了降低类之间的耦合。

    • 一个对象对其他对象保持最少了解。
    • 也叫【最少知道原则】,即一个被依赖的类不管多复杂,尽量将逻辑封装类内部,对外只提供public方法,不对外泄露任何信息。
    • 更简单定义:只与【直接朋友(两对象耦合即朋友,出现在成员变量,方法参数,方法返回值中的类为直接的朋友,出现在局部变量中的类不是直接的朋友,称陌生类)】通信。
    • 耦合方式:依赖、关联、组合、聚合等。
    • 陌生类最好不要以局部变量出现在类内部。

    非迪米特法则:

    //客户端
    public class Demeter1 {
        public static void main(String[] args) {
            //创建了一个 SchoolManager 对象
            SchoolManager schoolManager = new SchoolManager();
            //输出学院的员工id 和  学校总部的员工信息
            schoolManager.printAllEmployee(new CollegeManager());
        }
    }
    
    //学校总部员工类
    class Employee {
        private String id;
        public void setId(String id) { this.id = id; }
        public String getId() { return id; }
    }
    //学院员工类
    class CollegeEmployee {
        private String id;
        public void setId(String id) { this.id = id; }
        public String getId() { return id; }
    }
    
    //学院员工管理类
    class CollegeManager {
        //返回学院的所有员工
        public List<CollegeEmployee> getAllEmployee() {
            List<CollegeEmployee> list = new ArrayList<CollegeEmployee>();
            for (int i = 0; i < 10; i++) { //这里我们增加了10个员工到 list
                CollegeEmployee emp = new CollegeEmployee();
                emp.setId("学院员工id= " + i);
                list.add(emp);
            }
            return list;
        }
    }
    //学校管理类
    //分析 SchoolManager 类的直接朋友类有哪些 Employee、CollegeManager
    //CollegeEmployee 不是 直接朋友 而是一个陌生类,这样违背了 迪米特法则 
    class SchoolManager {
        //返回学校总部的员工
        public List<Employee> getAllEmployee() {
            List<Employee> list = new ArrayList<Employee>();
            for (int i = 0; i < 5; i++) { //这里我们增加了5个员工到 list
                Employee emp = new Employee();
                emp.setId("学校总部员工id= " + i);
                list.add(emp);
            }
            return list;
        }
    
        //该方法完成输出学校总部和学院员工信息(id)
        void printAllEmployee(CollegeManager sub) {
            //分析问题
            //1. 这里的 CollegeEmployee 不是  SchoolManager的直接朋友
            //2. CollegeEmployee 是以局部变量方式出现在 SchoolManager
            //3. 违反了 迪米特法则 
    
            //获取学院员工
            List<CollegeEmployee> list1 = sub.getAllEmployee();
            System.out.println("------------学院员工------------");
            for (CollegeEmployee e : list1) {
                System.out.println(e.getId());
            }
            //获取学校总部员工
            List<Employee> list2 = this.getAllEmployee();
            System.out.println("------------学校总部员工------------");
            for (Employee e : list2) {
                System.out.println(e.getId());
            }
        }
    }
    -----------结果
    ------------学院员工------------
    学院员工id= 0
    学院员工id= 1
    学院员工id= 2
    学院员工id= 3
    学院员工id= 4
    学院员工id= 5
    学院员工id= 6
    学院员工id= 7
    学院员工id= 8
    学院员工id= 9
    ------------学校总部员工------------
    学校总部员工id= 0
    学校总部员工id= 1
    学校总部员工id= 2
    学校总部员工id= 3
    学校总部员工id= 4
    

    遵循迪米特法则:

    //客户端
    public class Demeter1 {
        public static void main(String[] args) {
            System.out.println("~~~使用迪米特法则的改进~~~");
            //创建了一个 SchoolManager 对象
            SchoolManager schoolManager = new SchoolManager();
            //输出学院的员工id 和  学校总部的员工信息
            schoolManager.printAllEmployee(new CollegeManager());
        }
    }
    
    //学校总部员工类
    class Employee {
        private String id;
        public void setId(String id) { this.id = id; }
        public String getId() { return id; }
    }
    //学院员工类
    class CollegeEmployee {
        private String id;
        public void setId(String id) { this.id = id; }
        public String getId() { return id; }
    }
    
    //学院员工管理类
    class CollegeManager {
        //返回学院的所有员工
        public List<CollegeEmployee> getAllEmployee() {
            List<CollegeEmployee> list = new ArrayList<CollegeEmployee>();
            for (int i = 0; i < 10; i++) { //这里我们增加了10个员工到 list
                CollegeEmployee emp = new CollegeEmployee();
                emp.setId("学院员工id= " + i);
                list.add(emp);
            }
            return list;
        }
        //输出学院员工的信息
        public void printEmployee() {
            //获取到学院员工
            List<CollegeEmployee> list1 = getAllEmployee();
            System.out.println("------------学院员工------------");
            for (CollegeEmployee e : list1) {
                System.out.println(e.getId());
            }
        }
    }
    //学校管理类
    //分析 SchoolManager 类的直接朋友类有哪些 Employee、CollegeManager
    //CollegeEmployee 不是 直接朋友 而是一个陌生类,这样违背了 迪米特法则
    class SchoolManager {
        //返回学校总部的员工
        public List<Employee> getAllEmployee() {
            List<Employee> list = new ArrayList<Employee>();
    
            for (int i = 0; i < 5; i++) { //这里我们增加了5个员工到 list
                Employee emp = new Employee();
                emp.setId("学校总部员工id= " + i);
                list.add(emp);
            }
            return list;
        }
    
        //该方法完成输出学校总部和学院员工信息(id)
        void printAllEmployee(CollegeManager sub) {
            //分析问题
            //1. 将输出学院的员工方法,封装到CollegeManager
            sub.printEmployee();
    
            //获取到学校总部员工
            List<Employee> list2 = this.getAllEmployee();
            System.out.println("------------学校总部员工------------");
            for (Employee e : list2) {
                System.out.println(e.getId());
            }
        }
    }
    ----------结果
    ~~~使用迪米特法则的改进~~~
    ------------学院员工------------
    学院员工id= 0
    学院员工id= 1
    学院员工id= 2
    学院员工id= 3
    学院员工id= 4
    学院员工id= 5
    学院员工id= 6
    学院员工id= 7
    学院员工id= 8
    学院员工id= 9
    ------------学校总部员工------------
    学校总部员工id= 0
    学校总部员工id= 1
    学校总部员工id= 2
    学校总部员工id= 3
    学校总部员工id= 4
    

    七、合成复用原则

    • 尽量使用组合/聚合的方式,不是使用继承。

    类之间关系

    所有都可以归为依赖。

    • 依赖:
      • 通过方法参数传递依赖进来。
      • 类中用到了对方。
      • 类成员属性
      • 方法返回类型
      • 方法接收参数
      • 方法中使用
    • 泛化(继承):依赖关系的特例
      • 泛化关系就是继承关系。
    • 实现:依赖关系的特例
      • 类实现了某一接口。
    • 关联:依赖关系的特例。类与类之间的联系。
      • 具有导航性(单向或双向),多重性(一对一、一对多等)
      • 如人和身份证类
    • 聚合:关联关系的特例。
      • 添加类属性,通过方法设置进来。
      • 整体与部分的关系,整体与部分可以分开。即可有可无。
      • 导航性(a聚合b还是b聚合类),多重性(单聚合-聚合一个还是多聚合-聚合多个)
      • 如:人和身份证、台式主机和显示器。
    • 组合:关联关系的特例。
      • 构建属性直接new一个出来。
      • 整体与部分的关系,整体与部分不可分开。
      • 随着当前类创建而创建,销毁而销毁。
      • 如:人和自己的头
      • 级联删除即组合关系。

    设计原则核心思想

    • 找出可能需变化之处独立出来,不要和不需变化的代码混写。
    • 针对接口编程,不是具体实现类编程
    • 为交互对象之间低耦合设计努力
  • 相关阅读:
    _bzoj1061 [Noi2008]志愿者招募【最小费用最大流】
    _bzoj2243 [SDOI2011]染色【树链剖分】
    _bzoj1013 [JSOI2008]球形空间产生器sphere【高斯消元】
    _bzoj1002 [FJOI2007]轮状病毒【瞎搞】
    leetcode 273 Integer to English Words
    leetcode 12 Integer to Roman
    leetcode 1071 Greatest Common Divisor of Strings
    lc6 ZigZag Conversion
    lc13 Roman to Integer
    leetcode 171 Excel Sheet Column Number
  • 原文地址:https://www.cnblogs.com/xiaoaiying/p/14163687.html
Copyright © 2011-2022 走看看