七大设计原则Java实战之个人理解和图解

目录

前言

一、单一职责原则

二、开闭原则

三、接口隔离原则

四、依赖倒置原则

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

六、里氏替换原则

七、组合优于继承原则

总结


前言

我们知道23个开发设计模式对应七大设计原则(单一职责原则、里氏替换原则、依赖倒置原则、开闭原则、迪米特法则(最少知道原则)、接口隔离原则、组合优于继承原则)。是前人总结下来的,以下是我个人Java实战学习,包含自己的个人理解和图解。中间串插了些基础知识个人记录。以便lz自己回顾时巩固。


​其他设计模式和设计原则

建造者模式实战图解

工厂模式演变实战图解】 ​

一、单一职责原则

理解:一个类对应一个职责(封装高频代码块),事不过三,三则重构。优点:1、代码重用性提高 2、代码可读性提高

例子:统计一个文本文件中,有多少个单词!这里1、读取文本文件内容,2、筛选单词。两个步骤代码封装为单一职责函数,符合单一职责原则。

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.Reader;
import java.util.Arrays;

/**
 * @Author yz
 * @Date 2022/9/6 17:25
 **/
public class AppTest {
    public static String loadFile(String path) throws Exception {
        Reader in = new FileReader(path);
        BufferedReader br = new BufferedReader(in);
        String line;
        StringBuilder sb = new StringBuilder();
        while((line=br.readLine())!=null){
            sb.append(line);
            sb.append(" ");
        }
        br.close();
        return sb.toString();
    }
    public static String[] getWords(String strs){
        return strs.split("[^a-zA-Z]+");
    }
    public static void main(String[] args) throws Exception {
        //统计一个文本文件中,有多少个单词
        //1、读取文本文件内容
        String str = loadFile("F:\\1.txt");
        //2、筛选单词
        String[] strs = getWords(str);
        //System.out.println("文本中含有单词"+strs.length+"个:"+ Arrays.toString(strs));
        System.out.println("文本中含有单词"+strs.length+"个:\n"+ String.join(",",strs));
    }
}

知识点个人记录

    ①底层知识点:GBK码表、UTF-8码表、unicode码表(java通过字符流FileReader读取文件时,读出来是字符对应的ASCLL码数字->然后到GBK码表找到对应的汉字->再去unicode码表中转成数字)
例如:45489---->gbk---->北---->unicode---->21271

    ②基础知识点:string的split函数的参数支持正则表达式。 

二、开闭原则

理解:对扩展新功能开放、对修改原功能关闭。简单理解为:类不变,在原类基础上扩展,添加改变类的属性或方法的扩展类或函数。

例子:创建汽车类Car,打印类对象价格,如果汽车打八折:符合开闭原则的方式(创建Car的子类,重写getPrice方法),不符合开闭原则的方式(直接修改Car类的getPrice方法),扩展到平时,如果Car类是jar包或者依赖导入进来的,就不能修改Car类代码了,而要符合开闭原则。

import lombok.Data;

import java.io.Serializable;

/**
 * @Author yz
 * @Date 2022/9/6 19:32
 **/
@Data
public class Car implements Serializable {

    private String brand;//品牌
    private String color;//颜色
    private boolean louyou;//漏油
    private Double price;//价格

    public Double getPrice() {
        //不符合开闭原则return price*0.8
        return price;
    }
    public void setPrice(Double price) {
        this.price = price;
    }
}
/**
 * @Author yz
 * @Date 2022/9/6 19:40
 **/
public class CountCarPrice extends Car {
    @Override
    public Double getPrice() {
        return super.getPrice()*0.8;
    }
}
/**
 * @Author yz
 * @Date 2022/9/6 19:35
 **/
public class Test {
    public static void main(String[] args) {
        Car car = new Car();
        car.setBrand("奔驰");
        car.setColor("黑色");
        car.setLouyou(true);
        car.setPrice(666666.0);
        System.out.println(car.getPrice());
        //变化来了,汽车打八折
        //符合开闭原则:可以这样做,创建Car的子类,重写Car的getPrice方法
        Car newCar = new CountCarPrice();
        newCar.setBrand("奔驰");
        newCar.setColor("黑色");
        newCar.setLouyou(true);
        newCar.setPrice(888888.0);
        System.out.println(newCar.getPrice());
        //不符合开闭原则就是直接修改Car类的getPrice方法
    }
}

三、接口隔离原则

理解:官方解释:客户端不应该依赖它不需要的接口,一个类对另一个类的依赖应该建立在最小接口上(理解:避免制作一个总接口,应该把它们分成一些小接口)。

例子:比如动物接口Animal有三个函数eat(),swim(),fly(),创建一个类Dog实现Animal,这个时候fly()这个函数显然是不能实现的,狗怎么会飞呢,创建一个类Bird实现Animal,这个时候swim()这个函数显然不能实现的,鸟怎么会游泳呢。解决:三个函数eat(),swim(),fly()分别作为一个接口,然后新增的动物会什么技能就实现什么接口,这样就符合接口隔离原则。

/**
 * @Author yz
 * @Date 2022/9/7 8:55
 **/
public interface Animal {
    void eat();
    void swim();
    void fly();
}
/**
 * @Author yz
 * @Date 2022/9/7 8:56
 **/
public class Dog implements Animal{
    @Override
    public void eat() {
        System.out.println("狗啃骨头!");
    }

    @Override
    public void swim() {
        System.out.println("狗刨!");
    }

    @Override
    public void fly() {
        throw new RuntimeException("你行你来。。。你来飞!");
    }
}
/**
 * @Author yz
 * @Date 2022/9/7 8:59
 **/
public class Bird implements Animal{
    @Override
    public void eat() {
        System.out.println("鸟吃虫子!");
    }

    @Override
    public void swim() {
        throw new RuntimeException("You can you up,no can no BB!");
    }

    @Override
    public void fly() {
        System.out.println("鸟儿在飞!");
    }
}
/**
 * @Author yz
 * @Date 2022/9/7 8:52
 **/
public class InterfaceSolationMain {
    public static void main(String[] args) {
        Dog dog = new Dog();
        Bird bird = new Bird();
        //dog.fly();
        bird.swim();
    }
}

 图解:为上述例子作出图解

四、依赖倒置原则

理解:上层不能依赖于下层,它们都应该依赖于抽象,上层:调用别的方法就是上层(Person类),被调用的就是下层(Dog类)。

例子:创建一个Person类,有函数feed(Dog dog),创建一个Dog类,有函数eat()。业务场景:人喂狗食,狗吃食。这个时候新的需求:人喂猫食,用户创建一个Cat类,但是Person只有函数feed(Dog dog),如果你修改Person类,添加一个函数feed(Cat cat),这样就违背了开闭原则,我们希望的是:当下层新增一个动物时,上层应该”不知道“,上层代码应该不用改动。解决:这个时候中间添加一层接口Animal,Dog和Cat都实现Animal的eat()函数,Person则feed(Animal animal),这样就符合依赖导致原则。

/**
 * @Author yz
 * @Date 2022/9/7 8:55
 **/
public interface Animal {
    void eat();
}
/**
 * @Author yz
 * @Date 2022/9/7 8:56
 **/
public class Dog implements Animal{
    @Override
    public void eat() {
        System.out.println("狗啃骨头!");
    }
}
/**
 * @Author yz
 * @Date 2022/9/7 8:56
 **/
public class Cat implements Animal{
    @Override
    public void eat() {
        System.out.println("猫吃鱼!");
    }
}
/**
 * @Author yz
 * @Date 2022/9/7 10:12
 **/
public class Person {
    public void feed(Animal animal){
        animal.eat();
    }

    public static void main(String[] args) {
        Animal dog = new Dog();
        Animal cat = new Cat();
        Person person = new Person();
        person.feed(dog);
        person.feed(cat);
    }
}

 图解:为上述例子作出图解

知识点个人记录
    ①类与类之间的关系:继承、关联(一个类实例对象作为另一个类的属性变量)、依赖(一个类的实例化对象作为另一个类的实参或形参)
    ②set集合使用:set集合去重,set集合对象的toArray方法(不加参数默认返回Object数组,且无法强转)加一个new不同类型数组为参数时,返回该数组类型(set.toArray(new String[]{})则返回String数组,传new Integer[]{}则返回Integer数组)
    
    ③list使用:list的toArray方法同set一样,list的retainAll方法取交集(常用方法):list1.retainAll(list2)解释list2内容不变,交集存入到list1中
    
    ④类的实例化顺序:1、执行静态块 2、执行构造代码块 3、执行构造器 注意:类中的实例成员,等价于构造代码块。类中的静态成员等价于静态块。注意:静态方法不算静态块
    
    ⑤线程基础:实现Runnable接口run方法(继承Thread类,线程池创建线程和Callable创建线程本质上是通过线程工厂创建线程的,默认采用 DefaultThreadFactory),synchronized同步锁,锁对象.notifyAll()唤醒线程,wait()等待线程进入阻塞,问:线程如何通信?答:notify,wait,共享对象(java中每一个对象都有一个对象互斥锁)
    
注意:面试题-dao层结构(可以解释依赖倒置原则、开闭原则、多态)业务层——>dao接口层——>dao的实现层

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

理解:一个类对于其他类,要知道的越少越好。(封装的思想)

特点:只和朋友通信:朋友定义(a、类中的属性 b、方法的参数 c、方法的返回值 d、方法中实例化出来的对象)。

缺点:系统中为了做朋友造出大量的小方法,这些方法仅仅是传递间接的调用,与系统的业务逻辑无关,造成不同模块之间通信效率降低,模块之间不容易协调。

例子:创建一个类Computer(三个public方法:保存数据、杀死进程、关闭屏幕),创建一个类Person(属性new Computer,方法shutDownCompoter(){调用Computer类中的三个方法进行业务关机操作})。问题:此时这个Person对于Computer的细节知道的太多了,代码复杂度就提升了,万一用户使用不当,就有可能造成更大的损失。解决:类Computer的三个方法设为private私有,再新增一个public关机方法(调用私有的三个方法)对外,这样就符合最少知道原则。

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.Reader;
import java.util.Arrays;

/**
 * @Author yz
 * @Date 2022/9/6 17:25
 **/
class Computer{
    private void saveData(){
        System.out.println("保存数据!");
    }
    private void killProcess(){
        System.out.println("杀死进程!");
    }
    private void closeScreen(){
        System.out.println("关闭屏幕!");
    }
    public void shutdownComputer(){
        saveData();
        killProcess();
        closeScreen();
        System.out.println("关机成功!");
    }
}
public class AppTest {
    private Computer comp = new Computer();

    public void shutdownComputer(){
        comp.shutdownComputer();
    }

    public static void main(String[] args) {
        AppTest appTest = new AppTest();
        appTest.shutdownComputer();
    }
}

六、里氏替换原则

理解:子类对象透明的替换父类对象(正方形不是长方形(is-a的关系)反例子,正方形继承长方形,但是有些业务场景不允许(比如修改长方形的宽,长不变),就不符合该原则,类似例子”鸵鸟非鸟“)

特点:方法重写:在子类和父类中,出现了返回类型相同、方法名相同、方法参数相同的方法时,构成方法重写(限制:a、子类重写父类的方法时,子类方法的访问修饰符不能比父类更严格 b、子类方法不能抛出比父类更多的异常:两点限制就是为了保证符合里氏替换原则)

例子:反例子,正方形不是长方形

import lombok.Data;
/**
 * @Author yz
 * @Date 2022/9/7 20:28
 **/
@Data
public class Rectangle{
    private double length;
    private double width;
}
/**
 * @Author yz
 * @Date 2022/9/7 20:29
 **/
public class Square extends Rectangle{
    private double sidelength;
    @Override
    public double getLength(){
        return sidelength;
    }
    @Override
    public void setLength(double length) {
        this.sidelength = length;
    }
    @Override
    public double getWidth() {
        return sidelength;
    }
    @Override
    public void setWidth(double width) {
        this.sidelength = width;
    }
}
/**
 * @Author yz
@Date 2022/9/6 17:25
 **/
class Utils{
    public static void transform(Rectangle rectangle){
        //需求:增加长方形宽,直至宽大于长
        while(rectangle.getWidth()<=rectangle.getLength()){
            rectangle.setWidth(rectangle.getWidth()+1);
            System.out.println(rectangle.getWidth()+":"+rectangle.getLength());
        }
    }
}
public class AppTest {
    public static void main(String[] args) {
        Rectangle rect = new Rectangle();
        //以下new Square()的话,就不符合需求了,程序进入无限死循环
        //Rectangle rect = new Square();
        rect.setLength(20);
        rect.setWidth(12);
        Utils.transform(rect);
    }
}

七、组合优于继承原则

理解:又名合成复用原则,概念理解:继承,一个类继承另外一个类,组合,是类与类之间关联关系(细分组合,聚合。所谓组合是关系强,聚合是关系弱)中的组合。
为什么组合优于继承?答:我们自己写代码,继承、重写,随便使用,如果我们仅仅是为了复用代码,而继承别人的类,难免出现”沟通“上的问题

继承例子:需求,制作一个集合,要求该集合能记录曾今加过多少个元素。(不是统计某一时刻集合中有多少个元素),以下继承的例子,会发现我明明添加三条记录却是显示6条,原因是:HashSet 源码的addAll()方法会循环调用add()方法,即计数了两遍。为了满足需求我们可以不重写addAll()方法或者重写addAll方法确保count不用累加集合的size了。问题来了,万一jdk更新添加了addSome方法之类的,那为了满足需求我们又得查文档重写addSome方法。侧面反映出继承的弊端,对于我们写子类的用户有点苛刻了。

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

/**
 * @Author yz
@Date 2022/9/6 17:25
 **/
class Myset extends HashSet {
   private int count = 0;
   @Override
   public boolean add(Object o){
       count++;
       return super.add(o);
   }
   @Override
   public boolean addAll(Collection c){
       count+=c.size();
       return super.addAll(c);
   }
   public int getCount(){
       return count;
   }
}
public class AppTest {
    public static void main(String[] args) {
        Myset myset = new Myset();
        Set set = new HashSet();
        set.add("九阳神功");
        set.add("九阴真经");
        set.add("葵花宝典");
        myset.addAll(set);
        System.out.println(myset.getCount());
    }
}


组合例子:为了解决继承的弊端可以选择关联组合,可以将HashSet的对象作为Myset的属性字段,我们可以写自己的计数方法(调用HashSet的add方法和addAll方法,jdk更新后有addSome方法也不影响我们的需求了)。思考:组合这么好,那以后不使用继承和方法重写了吗,lz觉得父类作者和子类作者不是同一个人就不要去继承了,反之父类作者和子类作者是同一个人,那都是自己写的肯定都知道,可以去放手继承。

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

/**
 * @Author yz
 * @Date 2022/9/6 17:25
 **/
class Myset{
    private Set set = new HashSet();
    private int count = 0;

    public boolean add(Object o) {
        count++;
        return set.add(o);
    }
    public boolean addAll(Collection c){
        count+=c.size();
        return set.addAll(c);
    }
    public int getCount() {
        return count;
    }
}
public class AppTest {
    public static void main(String[] args) {
        Myset myset = new Myset();
        Set set = new HashSet();
        set.add("九阳神功");
        set.add("九阴真经");
        set.add("葵花宝典");
        myset.addAll(set);
        myset.add("太极拳");
        System.out.println(myset.getCount());
    }
}

总结

总结发现七大设计原则离不开一个字"分",由"分"字联想到代理,再联想到网络接口,再联想到我们平常的生活和学习,遇到一件难事,拆"分"它为一件件小事,其中某几件事需要请求朋友(可理解为代理、接口)帮助。

-----结束语: 如果做某件事可以让你快乐,那只管放手去做就好

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

热心码民阿振

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值