& 七大设计原则

七大设计原则

  • 单一职责原则
  • 里氏替换原则
  • 依赖倒置原则
  • 开闭原则
  • 迪米特法则(最少知道原则)
  • 接口隔离原则
  • 组合优于继承原则

单一职责原则

每个类每个方法只做一件事

优点:代码的重用性

开闭原则

  • 对扩展新功能是开放的
  • 对修改原有功能是关闭的

比如:
有一个刮胡刀,它的作用就是刮胡子,现在想让刮胡刀具备吹风机的功能。
违反开闭原则:把吹风功能加上了,可能不能刮胡子了。
符合开闭原则:把吹风功能加上了,且不影响刮胡子的功能。

接口隔离原则

避免制作一个总接口,应该把它们分成一些小接口

使用多个专门的接口比使用单一的总接口要好。
一个类对另外一个类的依赖性应当是建立在最小的接口上的。
一个接口代表一个角色,不应当将不同的角色都交给一个接口。没有关系的接口合并在一起,形成一个臃肿的大接口,这是对角色和接口的污染。

依赖倒置原则

上层不能依赖下层,他们都应该依赖抽象

依赖倒置原则(Dependence Inversion Principle)是程序要依赖于抽象接口,不要依赖于具体实现。
简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。

依赖倒置原则包含的三层含义:

  • 高层模块不应该依赖底层模块,两者都应该依赖其抽象
  • 抽象不应该依赖细节
  • 细节应该依赖抽象

依赖倒置原则是基于这样的设计理念:**相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建的架构比以细节为基础的架构要稳定的多。****

在 java 中,抽象指的是接口或抽象类,细节就是具体的实现类

使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成

正反例子来自:https://blog.csdn.net/weixin_44594041/article/details/112727879

反例

在这里插入图片描述

class Email {
    public String getInfo() {
        return "电子邮件信息: hello,world";
    }
}
class Person {
    public void receive(Email email ) {
        System.out.println(email.getInfo());
    }
}
public class DependecyInversion {
    public static void main(String[] args) {
        Person person = new Person();
        person.receive(new Email());
    }
}

//增加微信
class WeiXin {
    public String getInfo() {
        return "微信信息: hello,ok";
    }
}

分析

完成Person接收消息的功能

  1. 如果我们获取的对象是 微信,短信等等则新增类,同时Perons也要增加相应的接收方法
  2. 解决思路:引入一个抽象的接口IReceiver, 表示接收者, 这样Person类与接口IReceiver发生依赖因为Email, WeiXin 等等属于接收的范围,他们各自实现IReceiver 接口就ok, 这样我们就符号依赖倒转原则

正例

在这里插入图片描述

//定义接口
interface IReceiver {
    public String getInfo();
}
class Email implements IReceiver {
    public String getInfo() {
        return "电子邮件信息: hello,world";
    }
}
//增加微信
class WeiXin implements IReceiver {
    public String getInfo() {
        return "微信信息: hello,ok";
    }
}
class Person {
    //这里我们是对接口的依赖
    public void receive(IReceiver receiver ) {
        System.out.println(receiver.getInfo());
    }
}
public class DependecyInversion {

    public static void main(String[] args) {
        //客户端无需改变
        Person person = new Person();
        person.receive(new Email());
        person.receive(new WeiXin());
    }
}

分析

  • 高层模块Persion没有依赖底层模块Email和WeiXin,而是依赖抽象(IReciver)
  • 细节(Email、Weixin)依赖抽象(IReciver)
  • 对比两个UML图可知:此时关系由依赖变为实现,UML图中的箭头方向也反方向发生变化,因此叫做依赖倒置

迪米特法则(最少知道原则)

迪米特法则也叫作最少知道原则(封装),一个类,对于其他类,要知道的越少越好,只和朋友通信。

  • 那什么是朋友:
    • 类中的字段
    • 方法的参数
    • 方法的返回值
    • 方法中实例化出来的对象

里氏替换原则

任何能使用父类的地方,都应该能透明的替换为子类对象。
也就是说,子类对象可以随时随地的替换父类对象,且替换完以后,语法不会报错,业务逻辑也不会出现问题!

反例:

package com.lhx.f_liskvo.negtive;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

/**
* 继承的作用:
 *  1. 提高代码的重用性
 *  2. 多态的前提
*
* 两个类能不能发生继承关系的依据是什么?
 *  a. 主要看有没有"is a"关系。
 *  b. 在两个类有了is a关系后,还要考虑子类对象在替换了父类对象之后,业务逻辑是否变化!如果变化,则不能发生继承关系。
*
* 例子如下:正方形长方形那些事
* 正方形和长方形有"is a"关系,那我们能不能让正方形类去直接继承长方形类呢?现在不能了!
* 为什么呢?因为还要考虑业务场景,看看在特定的业务场景下,正方形能替换了长方形以后,业务逻辑是否变化!
*
*
*/
class Utils {
    public static void transform(Rectangle r){
        while (r.getWidth() <= r.getLength()){
            r.setWidth(r.getWidth() + 1);
            System.out.println(r.getWidth() + ":" + r.getLength());
        }
    }
}

//长方形
@Getter
@Setter
@ToString
class Rectangle {
    private double length;
    private double width;
}

//正方形 重写父类长方形的方法
class Square extends Rectangle {
    private double sidelength;//边长

    @Override
    public void setLength(double length) {
        this.sidelength = length;
    }
    @Override
    public void setWidth(double width) {
        this.sidelength = width;
    }
    @Override
    public double getLength() {
        return sidelength;
    }
    @Override
    public double getWidth() {
        return sidelength;
    }
}

public class AppTest {
    public static void main(String[] args) {
        Rectangle rectangle1 = new Rectangle();
        rectangle1.setWidth(12);
        rectangle1.setLength(20);
        Utils.transform(rectangle1);

        //此时把父类替换为子类后 业务效果发生变化 出问题了 死循环!
        Rectangle rectangle2 = new Square();//向上转型 父类引用指向子类对象
        rectangle2.setWidth(12);
        rectangle2.setLength(20);
        Utils.transform(rectangle2);//出问题了 死循环!
    }
}

修改以上例子改为符合里氏替换原则:

将正方形Square不要继承长方形!(去掉extends Rectangle

组合优于继承原则

类和类之间有3种关系:

  1. 继承:就是一个类继承另外一个类
  2. 依赖
  3. 关联
    1. 组合(关系强)
    2. 聚合(关系弱)

组合优于继承中的组合,其实指的就是关联关系

a

package com.lhx.g_composite.d;

import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

/**
* 需求:制作一个集合,要求该集合能记录曾今加过多少个元素。(不是统计某一时刻集合中有多少个元素)
* 只对于c包的2个问题
* 修改代码如下:
* 1. 我们的MySet,再也不要去继承HashSet了
* 2. 取而代之,我们让MySet和HashSet发生关联关系(组合)
*/
class MySet {
    private int count = 0;
    private Set set = new HashSet();
    public boolean add(Object e){
        count++;
        return set.add(e);
    }
    public boolean addAll(Collection e){
        count+=e.size();
        return set.addAll(e);
    }
    public int getCount() {
        return count;
    }
}

public class AppTest {
    public static void main(String[] args) {
        MySet mySet = new MySet();
        mySet.add("1");
        System.out.println(mySet.getCount());
    }
}


/*
以上组合有点我们已经体会到了:
问题是:
1. 难道以后都不能使用继承了吗?
2. 难道以后都不能进行方法重写了吗?

如果父类作者,和子类的作者,不是同一个人,就别继承。
如果父类作者,和子类的作者,是同一个人,可以继承。

我们自己写代码,继承,重写随便使用。
但是如果我们仅仅为了复用代码,而继承别人的类,难免出现“沟通”上的问题。
*/

b

package com.lhx.g_composite.b;

import java.util.Collection;
import java.util.HashSet;

/**
* 需求:制作一个集合,要求该集合能记录曾今加过多少个元素。(不是统计某一时刻集合中有多少个元素)
* 只对于a包的问题,addAll会回调add方法,我们修改代码如下:
* 把addAll删除掉,不要重写父类HashSet的addAll了
* 反正父类的addAll本身就会去回调add
*/
class MySet extends HashSet {
    private int count = 0;
    @Override
    public boolean add(Object e){
        count++;
        return super.add(e);
    }
    public int getCount() {
        return count;
    }
}

public class AppTest {
    public static void main(String[] args) {
        MySet mySet = new MySet();
        mySet.add("a");
        mySet.add("b");
        mySet.add("c");

        MySet mySet2 = new MySet();
        mySet2.addAll(mySet);
        System.out.println(mySet2.getCount());
    }
}
//此时,这个代码好像看起来很完美了,已经满足了需求
//问题是:目前这个代码,必须依赖于这样一个事实,就是HashSet的addAll方法必须去回调add方法
//万一将来jdk版本中,HashSet的addAll实现代码,突然不回调add方法了,则在将来我们定义的这个MySet就被“撼动”!

//比如:HashMap,在jdk678中 底层实现就分别换了3次******


c

package com.lhx.g_composite.c;

import java.util.Collection;
import java.util.HashSet;

/**
* 需求:制作一个集合,要求该集合能记录曾今加过多少个元素。(不是统计某一时刻集合中有多少个元素)
* 只对于b包的问题,MySet必须依赖于这样一个事实:addAll必须回调add,但是jdk未来的版本中,不会做这样的保证
* 所以修改代码如下:我们自己重写addAll,保证addAll一定会调用add
*
*/
class MySet extends HashSet {
    private int count = 0;
    @Override
    public boolean add(Object e){
        count++;
        return super.add(e);
    }
    @Override
    public boolean addAll(Collection c) {
        //仿照jdk源码 重写addAll
        boolean modified = false;
        for (Object e : c)
            if (add(e))
                modified = true;
        return modified;
    }

    public int getCount() {
        return count;
    }
}

public class AppTest {
    public static void main(String[] args) {

        MySet mySet = new MySet();
        mySet.add("a");
        mySet.add("b");
        mySet.add("c");

        MySet mySet2 = new MySet();
        mySet2.addAll(mySet);

        System.out.println(mySet2.getCount());
    }
}
//此时,这个代码好像看起来很完美了,已经满足了需求
//问题是:
// 1.如果再新的jdk版本中,HashSet突然多了一个元素加入集合的入口方法:addSome 此时MySet根本没有重写addSome
//      当使用addSome方法添加元素的时候,根本不会去统计元素的数量
// 2.我们重写了addAll和add方法,但是在HashSet的所有方法中,难免会有一些其他方法,会依赖于addAll和add。
//      我们重写了别人类中的某些方法,就会导致其他依赖于这些方法的方法,出现问题!

d

package com.lhx.g_composite.d;

import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

/**
* 需求:制作一个集合,要求该集合能记录曾今加过多少个元素。(不是统计某一时刻集合中有多少个元素)
* 只对于c包的2个问题
* 修改代码如下:
* 1. 我们的MySet,再也不要去继承HashSet了
* 2. 取而代之,我们让MySet和HashSet发生关联关系(组合)
*/
class MySet {
    private int count = 0;
    private Set set = new HashSet();
    public boolean add(Object e){
        count++;
        return set.add(e);
    }
    public boolean addAll(Collection e){
        count+=e.size();
        return set.addAll(e);
    }
    public int getCount() {
        return count;
    }
}

public class AppTest {
    public static void main(String[] args) {
        MySet mySet = new MySet();
        mySet.add("1");
        System.out.println(mySet.getCount());
    }
}


/*
以上组合有点我们已经体会到了:
问题是:
1. 难道以后都不能使用继承了吗?
2. 难道以后都不能进行方法重写了吗?

如果父类作者,和子类的作者,不是同一个人,就别继承。
如果父类作者,和子类的作者,是同一个人,可以继承。

我们自己写代码,继承,重写随便使用。
但是如果我们仅仅为了复用代码,而继承别人的类,难免出现“沟通”上的问题。
*/

Stack(jdk源码中违反该原则的类)

Stack 栈
先进后出,后进先出

JDK源码中Stack想复用或重用Vector中的某些方法,但是却继承了Vector,
导致不需要的方法也会继承到Stack中,导致Stack像一个栈,但不是一个纯粹的栈。

package com.lhx.g_composite.supplement;

import java.util.Stack;

public class AppTest {
    public static void main(String[] args) {
        Stack<Object> stack = new Stack<>();
        stack.push(0);
        stack.push(1);
        stack.push(2);
        //栈:先进后出,后进先出
        // 如果按照这个约定 stack.get(0) 应该等于2
        // 但结果却为 0
        //所以它是个栈 但不是个纯粹的栈 因为它继承了Vector 导致了这样的后果
        System.out.println(stack.get(0));
    }
}
原文地址:https://www.cnblogs.com/doagain/p/14968990.html