设计模式学习笔记(二)面向对象原则

1、单一职责原则

单一职责原则(Single Responsibility Principle, SRP):一个类只负责一个功能领域中的相应职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因。
ps:顾名思义,每个类应该负责单一的职责,比如数据库连接类、订单类、购物车类等,单一职责原则是实现高内聚、低耦合的指导方针,它是最简单但又最难运用的原则。

2、开闭原则

开闭原则(Open-Closed Principle, OCP):一个软件实体应当对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展。
ps:开闭原则重点在于可以加一个抽象层,用这个抽象层来规定动作,如果想要扩展,不是修改源代码,而是新建一个类来继承这个抽象类。开-是指对扩展开放;闭-是指对源程序的修改关闭
例:
在这里插入图片描述

// (1)抽象类用来规范动作
public abstract class AbstractChart {
    public abstract void initChart();
    public abstract void display();
}
//(2)具体的实现类1
public class pieChart extends AbstractChart {
    @Override
    public void initChart() {
        System.out.println("初始化饼状图...");
    }
    @Override
    public void display() {
        System.out.println("绘制饼状图...");
    }
}
//(2)具体的实现类2
public class barChart extends AbstractChart {
    @Override
    public void initChart() {
        System.out.println("初始化柱状图...");
    }
    @Override
    public void display() {
        System.out.println("绘制柱状图...");
    }
}
(3)ChartDisplay作为统一入口
public class ChartDisplay {
    AbstractChart chart;
    public void setChart(AbstractChart chart) {
        this.chart = chart;
    }
    public void initChart(){
        chart.initChart();
    }
    public void display(){
        chart.display();
    }
}
(4)主程序test
		AbstractChart pie= new pieChart();
        ChartDisplay pieChart = new ChartDisplay();
        pieChart.setChart(pie);
        pieChart.initChart();
        pieChart.display();

        AbstractChart bar= new barChart();
        ChartDisplay barChart = new ChartDisplay();
        barChart.setChart(bar);
        barChart.initChart();
        barChart.display();

注:如果抽象层不需要做任何实现,建议使用接口,因为在Java等语言中的接口(interface)支持多重实现,可扩展性更好;如果在子类中有一些公共的行为,需要在父类中统一实现,此时可以使用抽象类,因为抽象类中既可以包含具体方法,也可以包含抽象方法。通常,能用接口的时候就应该尽量使用接口。

3、里氏代换原则

里氏代换原则(Liskov Substitution Principle, LSP):所有引用基类(父类)的地方必须能透明地使用其子类的对象。
在软件中将一个基类对象替换成它的子类对象,程序将不会产生任何错误和异常,反过来则不成立,如果一个软件实体使用的是一个子类对象的话,那么它不一定能够使用基类对象。在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象。
里氏代换原则是实现开闭原则的重要方式之一。
ps:尽量把父类设计为抽象类或接口。此原则的重点在于可以对基类拓展,但是不要修改基类原有的方法,达到所有引用基类的地方都可以透明地引用其子类
(1)子类的所有方法必须在父类中声明,或子类必须实现父类中声明的所有方法。根据里氏代换原则,为了保证系统的扩展性,在程序中通常使用父类来进行定义,如果一个方法只存在子类中,在父类中不提供相应的声明,则无法在以父类定义的对象中使用该方法。
(2) 我们在运用里氏代换原则时,尽量把父类设计为抽象类或者接口,让子类继承父类或实现父接口,并实现在父类中声明的方法,运行时,子类实例替换父类实例,我们可以很方便地扩展系统的功能,同时无须修改原有子类的代码,增加新的功能可以通过增加一个新的子类来实现。里氏代换原则是开闭原则的具体实现手段之一。
(3) Java语言中,在编译阶段,Java编译器会检查一个程序是否符合里氏代换原则,这是一个与实现无关的、纯语法意义上的检查,但Java编译器的检查是有局限的。

4、依赖倒转原则

依赖倒转原则(Dependency Inversion Principle, DIP):抽象不应该依赖于细节,细节应当依赖于抽象。换言之,要针对接口编程,而不是针对实现编程。
我们在代码中传递参数或者程序之间的调用时,尽量使用高层抽象类,即在抽象类中就定义好参数项以及返回类型,因此实现类中的方法都要在高层抽象类中事先声明,不要超出这些方法,因此我们如果想扩展功能,可以直接在抽象层增加声明。
例:
优化前:
在这里插入图片描述
优化后:
在这里插入图片描述
因此可以看出,使用依赖倒转原则后,我们不需要对CustomerDAO类进行修改,而是面向DataConvertor接口进行变成,符合依赖倒转原则。

注入方式:在实现依赖倒转原则时,我们需要针对抽象层编程,而将具体类的对象通过依赖注入(DependencyInjection, DI)的方式注入到其他对象中,依赖注入是指当一个对象要与其他对象发生依赖关系时,通过抽象来注入所依赖的对象。常用的注入方式有三种,分别是:构造注入设值注入(Setter注入)和接口注入。构造注入是指通过构造函数来传入具体类的对象,设值注入是指通过Setter方法来传入具体类的对象,而接口注入是指通过在接口中声明的业务方法来传入具体类的对象。这些方法在定义时使用的是抽象类型,在运行时再传入具体类型的对象,由子类对象来覆盖父类对象。

不使用依赖倒转原则代码示例:

public class Depend {
    public static void main(String[] args) {
        Person person = new Person();
        person.receive(new Email());
    }
}
class Email{
    public String getInfo(){
        return "邮件信息:你好,tom";
    }
}
 
/**
 * 1、这样做简单,容易
 * 2、当获取的对象发生变化时,就要增加新的类,Person类也要增加新的方法。
 * 解决方法:引入一个抽象的接口IReceiver,表示接受者,Person类与IReceiver发生依赖
 * Email类等各自实现IReceiver即可。
 */
class Person{
    public void receive(Email email){
        System.out.println(email.getInfo());
    }
}

三种注入方式示例
通过接口的方式实现依赖倒置示例代码:

public class Depend {
    public static void main(String[] args) {
        Person person = new Person();
        person.receive(new Email());
        person.receive(new Note());
 
    }
}
//定义接口
interface IReceiver{
    String getInfo();
}
class Email implements IReceiver{
    public String getInfo(){
        return "邮件信息:你好,tom";
    }
}
class Note implements IReceiver{
 
    @Override
    public String getInfo() {
        return "短信信息:你好,jack";
    }
}
class Person{
    public void receive(IReceiver iReceiver){
        System.out.println(iReceiver.getInfo());
    }
}

通过构造方法的方式实现依赖倒置示例代码:

public class Depend {
    public static void main(String[] args) {
        Person person = new Person(new Email());
        String info = person.getInfo();
        System.out.println(info);
    }
}
//定义接口
interface IReceiver{
    String getInfo();
}
interface Content {
         String open();
}
class Person implements IReceiver{
    public Content content;
    public Person(Content content){
        this.content = content;
    }
 
    @Override
    public String getInfo() {
       return this.content.open();
    }
}
class Email implements Content{
 
    @Override
    public String open() {
        return "邮件信息:你好,tom";
    }
}

通过setter方法的方式实现依赖倒置示例代码:

public class Depend {
    public static void main(String[] args) {
        Email email = new Email();
        Person person = new Person();
        person.setContent(email);
        String info = person.getInfo();
        System.out.println(info);
    }
    }
//定义接口
interface IReceiver{
    String getInfo();
    void setContent(Content content);
}
interface Content {
    String open();
}
class Person implements IReceiver{
    public Content content;
    public void setContent(Content content){
        this.content = content;
    }
 
    @Override
    public String getInfo() {
        return this.content.open();
    }
 
}
class Email implements Content {
 
    @Override
    public String open() {
        return "邮件信息:你好,tom";
    }
}

5、接口隔离原则

接口隔离原则(Interface Segregation Principle, ISP):使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口。
(1) 当把“接口”理解成一个类型所提供的所有方法特征的集合的时候,这就是一种逻辑上的概念,接口的划分将直接带来类型的划分。可以把接口理解成角色,一个接口只能代表一个角色,每个角色都有它特定的一个接口,此时,这个原则可以叫做“角色隔离原则”。
(2) 如果把“接口”理解成狭义的特定语言的接口,那么ISP表达的意思是指接口仅仅提供客户端需要的行为,客户端不需要的行为则隐藏起来,应当为客户端提供尽可能小的单独的接口,而不要提供大的总接口。在面向对象编程语言中,实现一个接口就需要实现该接口中定义的所有方法,因此大的总接口使用起来不一定很方便,为了使接口的职责单一,需要将大接口中的方法根据其职责不同分别放在不同的小接口中,以确保每个接口使用起来都较为方便,并都承担某一单一角色。接口应该尽量细化,同时接口中的方法应该尽量少,每个接口中只包含一个客户端(如子模块或业务逻辑类)所需的方法即可,这种机制也称为“定制服务”,即为不同的客户端提供宽窄不同的接口。
总的来说:每个接口应该承担一种相对独立的角色,不干不该干的事,该干的事都要干。在使用接口隔离原则时,我们需要注意控制接口的粒度,接口不能太小,如果太小会导致系统中接口泛滥,不利于维护;接口也不能太大,太大的接口将违背接口隔离原则,灵活性较差,使用起来很不方便。

6、合成复用原则

合成复用原则(Composite Reuse Principle, CRP):尽量使用对象组合,而不是继承来达到复用的目的。
复用时要尽量使用组合/聚合关系(关联关系),少用继承。
请参考合成复用原则

7、迪米特法则

迪米特法则(Law of Demeter, LoD):一个软件实体应当尽可能少地与其他实体发生相互作用。
迪米特法则可降低系统的耦合度,使类与类之间保持松散的耦合关系。
迪米特法则还有几种定义形式,包括:不要和“陌生人”说话、只与你的直接朋友通信等,在迪米特法则中,对于一个对象,其朋友包括以下几类:

  (1) 当前对象本身(this);
  (2) 以参数形式传入到当前对象方法中的对象;
  (3) 当前对象的成员对象;
  (4) 如果当前对象的成员对象是一个集合,那么集合中的元素也都是朋友;
  (5) 当前对象所创建的对象。

ps:
任何一个对象,如果满足上面的条件之一,就是当前对象的“朋友”,否则就是“陌生人”。在应用迪米特法则时,一个对象只能与直接朋友发生交互,不要与“陌生人”发生直接交互,这样做可以降低系统的耦合度,一个对象的改变不会给太多其他对象带来影响。
迪米特法则要求我们在设计系统时,应该尽量减少对象之间的交互,如果两个对象之间不必彼此直接通信,那么这两个对象就不应当发生任何直接的相互作用,如果其中的一个对象需要调用另一个对象的某一个方法的话,可以通过第三者转发这个调用。简言之,就是通过引入一个合理的第三者来降低现有对象之间的耦合度。
在将迪米特法则运用到系统设计中时,要注意下面的几点:在类的划分上,应当尽量创建松耦合的类,类之间的耦合度越低,就越有利于复用,一个处在松耦合中的类一旦被修改,不会对关联的类造成太大波及;在类的结构设计上,每一个类都应当尽量降低其成员变量和成员函数的访问权限;在类的设计上,只要有可能,一个类型应当设计成不变类;在对其他类的引用上,一个对象对其他对象的引用应当降到最低。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值