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

本文详细介绍了软件设计中的七大原则,包括单一职责、接口隔离、依赖倒置、里氏替换、开闭、迪米特和合成复用原则,并通过示例说明了如何在实际编程中应用这些原则,以提高代码的可维护性、扩展性和灵活性。同时,文章探讨了设计模式的重要性和如何通过23种经典设计模式遵循这些原则来优化软件设计。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

设计模式的目的

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

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、付费专栏及课程。

余额充值