zoukankan      html  css  js  c++  java
  • & 七大设计原则

    七大设计原则

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

    单一职责原则

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

    优点:代码的重用性

    开闭原则

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

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

    接口隔离原则

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

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

    依赖倒置原则

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

    依赖倒置原则(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));
        }
    }
    
  • 相关阅读:
    进程和线程的概念、区别和联系
    Python高级语法-GIL-理解(4.1.1)
    Web服务器-并发服务器-Epoll(3.4.5)
    Web服务器-并发服务器-长连接(3.4.4)
    Web服务器-并发服务器-单进程单线程非堵塞方式(3.4.3)
    Web服务器-并发服务器-协程 (3.4.2)
    Web服务器-并发服务器-多进程(3.4.1)
    Web服务器-服务器开发-返回浏览器需要的页面 (3.3.2)
    Web服务器-服务器开发-返回固定页面的HTTP服务器(3.3.1)
    Web服务器-HTTP相关-快速整一个服务器响应浏览器(3.2.1)
  • 原文地址:https://www.cnblogs.com/doagain/p/14968990.html
Copyright © 2011-2022 走看看