设计模式-原则

七大设计原则

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

一、单一职责

  • 降低类复杂度,一个类负责一项职责。
  • 提高类可读性,可维护性。
  • 降低变更引起的风险。
  • 通常情况下,应遵循。只有逻辑够简单,才可在代码级别(通过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一个出来。
    • 整体与部分的关系,整体与部分不可分开。
    • 随着当前类创建而创建,销毁而销毁。
    • 如:人和自己的头
    • 级联删除即组合关系。

设计原则核心思想

  • 找出可能需变化之处独立出来,不要和不需变化的代码混写。
  • 针对接口编程,不是具体实现类编程
  • 为交互对象之间低耦合设计努力
posted @ 2020-12-20 16:12  小艾影  阅读(71)  评论(0编辑  收藏  举报