设计模式与七大设计原则及其关系

设计模式的目的

解耦合,高内聚,提高程序的可维护性,扩展性,复用性,灵活性

23种设计模式 遵从的原则就是 7大设计原则

7大设计原则

  1. 单一职责原则
  2. 接口隔离原则
  3. 依赖倒置原则
  4. 里氏替换原则
  5. 开闭原则
  6. 迪米特法则
  7. 合成复用原则


单一职责原则

一个模块或一个类,甚至一个方法只应该负责一个功能

  1. 降低类的复杂度
  2. 提高类的可读性和可维护性
  3. 降低代码变动对类的影响产生的风险
  4. 通常应当遵从单一职责原则,只有类中方法很少,逻辑足够简单时,我们可以降低到在类的方法层面上去实现单一职责原则

单一职责原则可以在类的层面上实现,也可以通过方法的层面上实现

接口隔离原则

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

错误示例(上)和正确示例(下)

依赖倒置原则

  1. 高层模块不应该依赖于底层模块,二者都应该依赖于抽象
  2. 抽象不应该依赖于细节,细节应该依赖于抽象

依赖倒置原则的中心思想是面向接口编程

错误示例

class Email{
    public String getInfo(){
        return "Email Message";
    }
}

class Person{
    public void receive(Email email){
        System.out.println(email.getInfo());
    }
}

分析:
该方法较为简单;
但是如果需要获取的信息对象是微信或者QQ,则需要新增类并且还需要修改Person相应的接收方法

改善后的代码:

//新增的接口
interface IReceiver{
    public String getInfo();
}

class Email implements IReceiver{
    public String getInfo(){
        return "Email Message";
    }
}

//新增的微信消息类也只需要实现IReceiver接口即可
class Wechat implements IReceiver{
    public String getInfo(){
        return "Wechat Message";
    }
}

//接收消息类只需要依赖接口,而不需要在增加类时进行方法的修改
class Person{
    public void receive(IReceiver receiver){
        System.out.println(receiver.getInfo());
    }
}

依赖关系的传递在Java开发中有三种方式

  • 接口传递依赖
  • 构造方法传递依赖
  • setter方式传递依赖

接口传递依赖实例代码

interface IOpenAndClose{
    void open(ITV tv);
}

interface ITV{
    void play();
}

class OpenAndClose implements IOpenAndClose{
    public void open(ITV tv){
        tv.play();
    }
}

构造方法传递依赖实例代码

interface IOpenAndClose{
    void open();
}

interface ITV{
    void play();
}

class OpenAndClose implements IOpenAndClose{
    public ITV tv;

    public OpenAndClose(ITV tv){
        this.tv = tv;
    }

    public void open(){
        this.tv.play();
    }
}

setter方法传递依赖实例代码

interface IOpenAndClose{
    void open();
    void setTv(ITV tv);
}

interface ITV{
    void play();
}

class OpenAndClose implements IOpenAndClose{
    private ITV tv;

    public void setTV(ITV tv){
        this.tv = tv;
    }

    public void open(){
        this.tv.play();
    }
}

注意事项
低层模块都要有其对应的抽象类和接口,或者二者都有,这样的代码稳定性更好

里氏替换原则

产生背景

继承在给程序带来遍历的时候,也带来了弊端。继承对于程序有入侵性,增加了对象间的耦合性。如果一个类被其他类继承;当这个类需要修改时,必须要考虑到所有子类;并且当父类修改后,涉及到子类的功能可能产生故障。

  1. 使用继承时,遵从里氏替换原则,在子类中尽量不要重写父类的方法
  2. 继承使两个类的耦合性增强了,在适当的情况下,我们应该通过聚合,组合,依赖来解决问题

错误示例

class A{
    public int fun1(int a,int b){
        return a - b;
    }
}

class B extends A{
    public int fun1(int a,int b){
        return a + b;
    }
}
//B 类 错误 或者 不小心 地重写了父类的方法导致程序错误

修改后的代码

 

class Base{
    //更加基础的方法
}

class A extends Base{
    public int fun1(int a,int b){
        return a - b;
    }
}

class B extends Base{
    private A a = new A();

    public int fun1(int a,int b){
        return a + b;
    }

    //仍然需要A中的fun1,但不需要进行重写
    public int fun3(int a,int b){
        return this.a.fun1(a,b);
    }
}
//这里使用的组合的方法,降低了类之间的耦合度
//同时因为B不再继承于A,因此程序员不会再认为B中fun1像A中一样做减法了

开闭原则

最重要的一个原则,前面的多个原则都是为了实现开闭原则

  1. 一个软件中的类,模块,函数应该对扩展开发(对提供方),对修改关闭(对使用方)
  2. 用抽象构建框架,用实现扩展细节
  3. 当软件需要变化时,最好是通过扩展软件实体来实现,而不是通过修改原有的代码来实现

错误示例

class GraphicEditor{
    public void drawShape(Shape s){
        if(s.m_type == 1){
            drawRectangle(s);
        }else if(s.m_type == 2){
            drawCircle(s);

        //新增的三角形
        }else if(s.m_type == 3){
            drawTriangle(s);
        }
    }

    public void drawRectangle(Shape r){
        System.out.println("绘制矩形");
    }

    public void drawCircle(Shape r){
        System.out.println("绘制圆形");
    }

    //新增的方法
    public void drawTriangle(Shape r){
        System.out.println("绘制三角形");
    }
}

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;
    }
}

从上述代码可以容易的看出,每当要新增一个形状,不只要新增一个类,而且对于Shape的使用方GraphicEditor的内部代码也需要修改,这明显违背了开闭原则

改进后的代码

class GraphicEditor{
    public void drawShape(Shape s){
        s.draw();
    }
}

class Shape{
    int m_type;

    public void draw();
}

class Rectangle extends Shape{
    Rectangle(){
        super.m_type = 1;
    }

    @Override
    public void draw(){
        System.out.println("绘制矩形");
    }
}

class Circle extends Shape{
    Circle(){
        super.m_type = 2;
    }

    @Override
    public void draw(){
        System.out.println("绘制圆形");
    }
}

//新增的三角形
class Triangle extends Shape{
    Triangle(){
        super.m_type = 3;
    }

    @Override
    public void draw(){
        System.out.println("绘制三角形");
    }
}

这样使用方法的代码不会因为新增类的变动而修改代码
现在回头看改善后的依赖倒置原则和该示例非常相近,都是使用一个抽象类(接口)作为一组关联关系的依赖

迪米特法则

  1. 一个类对自己依赖的类所知的越少越好,即被依赖的类不管多复杂都应该进行封装,将一个简单的接口交给其他类来使用

迪米特法则还有一个更加简单的定义:只与直接朋友通信

直接朋友定义:

如果类A中具有一个属性B;
如果类A有一个方法需要变量B;
如果类A有一个方法的返回值为B;

这样的 B 称为 A 的直接朋友

陌生的类最好不要以局部变量的类的形式出现在类的内部
例如:

class A{
    publib void fun1(){
        B b = new B();
    }
}

错误示例

class A{
    public String id;

    public A(String id){
        this.id = id;
    }
}

class B{
    public String id;

    public A(String id){
        this.id = id;
    }
}

class A_Printer{
    public List<A> getA_List(){
        List<A> list = new ArrayList<>();
        for(i=0;i=3;i++){
            A a = new A(i):
            list.add(a);
        }
        return list;
    }
}

class B_Printer{
    public List<B> getB_List(){
        List<B> list = new ArrayList<>();
        for(i=0;i=3;i++){
            B a = new B(i):
            list.add(a);
        }
        return list;
    }

    public void printA_and_B(A_Printer aprinter){
        //printA
        List<A> AList = aprinter.getA_List();
        for(i=0;i=3;i++){
            for(A a:AList){
                System.out.println(a.id);
            }
        }

        //printB
        List<B> BList = this.getB_List();
        for(i=0;i=3;i++){
            for(B b:BList){
                System.out.println(b.id);
            }
        }
    }
}

//简写main函数
main(){
    B_Printer b = new B_Printer();
    b.printA_and_B(new A_Printer);
}

明显可见 List<A> AList 并不是类B的直接朋友,这使得类之间的耦合度上升了
因此需要对这一部分的代码进行修改

修改思路:只需要把不是直接朋友的类那一部分去掉,也就是说注释中 //PrintA 的那一段不应该直接出现在方法printA_and_B中,而应该由类A_Printer实现并通过某个方法直接提供给类B——Printer来使用。

修改后的代码

class A{
    public String id;

    public A(String id){
        this.id = id;
    }
}

class B{
    public String id;

    public A(String id){
        this.id = id;
    }
}

class A_Printer{
    public List<A> getA_List(){
        List<A> list = new ArrayList<>();
        for(i=0;i=3;i++){
            A a = new A(i):
            list.add(a);
        }
        return list;
    }

    public void printA(){
        //printA
        List<A> AList = this.getA_List();
        for(i=0;i=3;i++){
            for(A a:AList){
                System.out.println(a.id);
            }
        }
    }
}

class B_Printer{
    public List<B> getB_List(){
        List<B> list = new ArrayList<>();
        for(i=0;i=3;i++){
            B a = new B(i):
            list.add(a);
        }
        return list;
    }

    public void printA_and_B(A_Printer aprinter){
        //printA
        A.printA();

        //printB
        List<B> BList = this.getB_List();
        for(i=0;i=3;i++){
            for(B b:BList){
                System.out.println(b.id);
            }
        }
    }
}

//简写main函数
main(){
    B_Printer b = new B_Printer();
    b.printA_and_B(new A_Printer);
}

合成复用原则

中心思想:尽量使用合成或者聚合的方式,而不是继承的方式

依赖关系

class B{
    public void fun1(A a){
        a.fun2();
    }
}

聚合关系

class B{
    private A a;

    public void fun1(){
        a.fun2();
    }
}

组合关系

class B{
    private A a = new A();

    public void fun1(){
        a.fun2();
    }
}

设计原则的中心思想

  1. 找出程序中可能需要变化的地方,把它们独立出来,不要和不需要变化的代码混在一起
  2. 针对接口编程,而不是针对实现编程
  3. 为了交互对象之间的松耦合而努力
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值