Java基础 -- 七大基本原则

1、单一职责原则(Single-Responsibility Principle)

一个类应该只有一个能引起变化的原因,一个类的功能要单一,只做与它相关的事情

  • 职责过多,可能引起它变化的原因就越多,这将导致职责依赖,相互之间就产生影响,从而大大损伤其内聚性和耦合度

1.1 优点

  • 类的复杂性降低,实现什么职责都有清晰明确的定义
  • 可读性提高
  • 可维护性提高
  • 变更引起的风险降低,变更是必不可少的,如果接口的单一职责做得好,一个接口修改只对相应的实现类有影响,对其他的接口无影响,这对系统的扩展性、维护性都有非常大的帮助

1.2 示例

interface Human {
    void cry();
    void laugh();
    
    void punch();
    void kick();
}
  • cry 与 laugh 属于情绪一类的,punch 与 kick 属于动作一类,虽然都同属于 Human 接口,但两个分类之间关联并不大,还导致 Human 接口承担的过多的职责
interface Mood{
    void cry();
    void laugh();
}

interface Act{
    void punch();
    void kick();
}

2、开放封闭原则(Open-Closed principle)

软件实体(类,模块,函数等)应该是可扩展的,而不可修改的。实现该原则的核心思想就是对抽象编程,而不对具体编程,因为抽象相对稳定

  • 对扩展开放:有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况
    • 通过面向对象的继承和多态,使用重写来改变固有行为,实现新的拓展方法,所以扩展是开放的
  • 对修改封闭:类一旦设计完成,就可以独立完成其工作,而不要对其进行任何的修改
    • 让类依赖于固定的抽象,所以修改就是封闭的

2.1 优点

  • 有利于进行单元测试
  • 复用性提高
  • 代码可读性高,可维护性提高
  • 面向对象开发的要求
  • 保持软件内部的封装体系稳定,不被需求的变化影响

2.2 示例

interface Phone {
    void call();
}

class Nokia implements Phone {
    @Override
    public void call() {}
}
  • 早期的诺基亚只有打电话的功能,随着科技发展,又多了拍照、上网等功能
interface Phone {
    void call();
}

class Nokia implements Phone {
    @Override
    public void call() {}
}

class NokiaWP8 extends Nokia {
    @Override
    public void call() {}

    public void photograph() {}
    
    public void surfing() {}
}

3、里氏替换原则(Liskov-Substitution Principle)

子类应当可以替换父类并出现在父类能够出现的任何地方。这一思想体现为对继承机制的约束规范,只有子类能够替换父类时,才能保证系统在运行期内识别子类,这是保证继承复用的基础

  • 这一约束反过来则是不成立的,子类可以替换基类,但是基类不一定能替换子类
  • 子类可以扩展父类的功能,可以实现父类的抽象的方法,但不能覆盖改变父类的原有功能

该原则主要着眼于对抽象和多态建立在继承的基础上,因此只有遵循了里氏替换原则,才能保证继承复用是可靠的。实现的方法是面向接口编程:将公共部分抽象为接口或抽象类,在子类中通过覆写父类的方法实现新的方式支持同样的职责

  • 里氏替换原则是关于继承机制的设计原则,违反了该原则就必然导致违反开放封闭原则。 里氏替换原则能够保证系统具有良好的拓展性,同时实现基于多态的抽象机制,能够减少代码冗余,避免运行期的类型判断

3.1 优点

  • 代码共享,减少创建类的工作量,每个子类都拥有父类的方法和属性
  • 提高代码的重用性
  • 提高代码的可扩展性
  • 提高项目的开放性
  • 子类可以形似父类,但是又异于父类

3.2 示例

public class Demo {
    public static void main(String[] args) {
        Animal animal = new Animal();
        animal.say();	// 嗷~
        Animal human = new Human();
        human.say();	// 艹
    }
}

class Animal{
    void say() {
        System.out.println("嗷~");
    }
}

class Human extends Animal {
    @Override
    void say() {
        System.out.println("艹");
    }
}
  • 这个示例违反了里氏置换原则,子类方法覆盖了父类的方法
public class Demo {
    public static void main(String[] args) {
        Animal animal = new Animal();
        animal.say();	// 嗷~
        Animal human = new Human();
        human.say();	// 嗷~
    }
}

class Animal{
    void say() {
        System.out.println("嗷~");
    }
}

class Human extends Animal {
    @Override
    void say() {
        super.say();
    }
}

4、依赖倒置原则(Dependecy-Inversion Principle)

高层模块不依赖于底层模块,二者都同依赖于抽象;抽象不依赖于具体,具体依赖于抽象

  • 低层模块:不可分割的原子逻辑,可能会根据业务逻辑经常变化
  • 高层模块:低层模块的再组合,对低层模块的抽象

依赖一定会存在于类与类、模块与模块之间。当两个模块之间存在紧密的耦合关系时,最好的方法就是分离接口和实现:在依赖之间定义一个抽象的接口使得高层模块调用接口,而底层模块实现接口的定义,以此来有效控制耦合关系,达到依赖于抽象的设计目标

  • 抽象的稳定性决定了系统的稳定性,因为抽象是不变的,依赖于抽象是面向对象设计的精髓,也是依赖倒置原则的核心

4.1 优点

  • 通过依赖于接口,隔离了具体实现类
  • 低一层的变动并不会导致高一层的变动
  • 提高了代码的容错性、扩展性和易于维护

4.2 示例

public class Demo {
    public static void main(String[] args) {
        User user = new User();
        Phone phone = new Iphone();
        user.usePhone(phone);
    }
}

interface Phone {
    void call();
}

interface Human {
    void usePhone(Phone phone);
}

class Iphone implements Phone {
    @Override
    public void call() {
        System.out.println("calling...");
    }
}

class User extends Iphone implements Human {
    @Override
    public void usePhone(Phone phone) {
        phone.call();
    }
}

5、接口隔离原则(Interface-Segregation Principle)

客户端不应该依赖它不需要的接口,一个类对另一个类的依赖应该建立在最小的接口上。接口应该是内聚的,应该避免大接口。接口有效地将细节和抽象隔离,强调了接口的单一性

  • 单一职责原则要求是类和接口的职责单一,注重的职责,接口隔离原则是业务逻辑上的划分,要求接口的方法尽量少

大接口存在明显的弊端,必须得完全实现接口的所有方法、属性等;而某些时候,并不需要所有的接口定义,而且对大接口的修改将导致一连串的程序需要修改。将大接口分解为多个特点的定制化方法,使得客户端仅仅依赖于它们的实际调用的方法

分离的手段主要有两种

  • 委托分离,通过增加一个新的类型来委托客户的请求,隔离客户和接口的直接依赖,但是会增加系统的开销
  • 多重继承分离,通过接口多继承来实现客户的需求,推荐

5.1 优点

  • 避免接口污染
  • 灵活性提高
  • 提供定制服务
  • 实现高内聚

5.2 示例

interface Behavior {
    void eat();
    void fly();
    void speak();
}

class Bird implements Behavior {
    @Override
    public void eat() {}
    
    @Override
    public void fly() {}

    @Override
    public void speak() {}
}

class Human implements Behavior {
    @Override
    public void eat() {}
    
    @Override
    public void fly() {}

    @Override
    public void speak() {}
}
  • 人类不需要 fly 方法,鸟类也不需要 speak 方法,但有一个共有的 eat 方法,可以将行为接口拆分为共有的行为接口、人类特定行为接口、鸟类特定行为接口
interface Beahvior {
    void eat();
}

interface Behavior1 extends Beahvior {
    void fly();
}

interface Behavior2 extends Beahvior {
    void speak();
}

class Bird implements Behavior1 {
    @Override
    public void fly() {}

    @Override
    public void eat() {}
}

class Human implements Behavior2 {
    @Override
    public void eat() {}

    @Override
    public void speak() {}
}

6、最少知道原则 / 迪米特法则(Least Knowledge Principle)

一个对象应当对其他对象有尽可能少的了解,每个类尽量减少对其他类的依赖

该原则不希望类之间建立直接的接触,如果真的有需要建立联系,也希望能通过中间类来转达。因此,迪米特原则有可能会造成系统中存在大量的中介类,这些类都是为了传递类之间的相互调用关系,在一定程度上增加了系统的复杂度

6.1 优点

  • 减少对象之间的耦合性

6.2 示例

public class Demo {
    public static void main(String[] args) {
        Human human = new Human("Trump");
        Phone phone = new Phone();
        phone.call(new User(human));
    }
}

class Phone {
    void call(User user) {
        user.callSomeone();
    }
}

class Human {
    String name;

    public Human(String name) {
        this.name = name;
    }
}

class User {
    private Human human;

    public User(Human human) {
        this.human = human;
    }

    void callSomeone() {
        System.out.println("call: " + human.name);
    }
}

7 、组合 / 聚合复用原则(Composite / Aggregate Reuse Principle)

在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分,新对象通过向这些对象的委派达到复用已有功能的目的。尽量的使用合成和聚合,而不是继承关系达到复用的目的

  • 组合(Composition)表示一种强的拥有关系,体现了严格的部分和整体关系,部分和整体的生命周期一样

  • 聚合(Aggregation)表示一种弱的拥有关系,体现的是 A 对象可以包含 B 对象但 B 对象不是 A 对象的一部分

7.1 优点

  • 新的实现较为容易,因为超类的大部分功能可通过继承关系自动进入子类
  • 修改或扩展继承而来的实现较为容易

7.2 组合与继承

组合关系 继承关系
不破坏封装,整体类与局部类之间松耦合,彼此相对独立 破坏封装,子类与父类之间紧密耦合,子类依赖于父类的实现,子类缺乏独立性
具有较好的可扩展性 支持扩展,但是往往以增加系统结构的复杂度为代价
支持动态组合。在运行时,整体对象可以选择不同类型的局部对象 不支持动态继承。在运行时,子类无法选择不同的父类
整体类可以对局部类进行包装,封装局部类的接口,提供新的接口 子类不能改变父类的接口
整体类不能自动获得和局部类同样的接口 子类能自动继承父类的接口
创建整体类的对象时,需要创建所有局部类的对象 创建子类的对象时,无须创建父类的对象
  • 建议在同样可行的情况下,优先使用组合而不是继承。因为组合更安全,更简单,更灵活,更高效
    • 只有当子类真正是超类的子类型时,才适合用继承

7.3 示例

  • 组合:Human 与 Air 是强拥有关系,人类不能离开空气
class Human {
}

class Air {
    private Human human;

    public Air() {
        human = new Human();
    }
}
  • 聚合:Phone 与 Human 是弱拥有关系,人类可以不用手机
class Phone {
}

class Human {
    private Phone phone;

    public Human() {
        phone = new Phone();
    }
}
posted @ 2020-09-17 16:45  holyholic704  阅读(405)  评论(0编辑  收藏  举报