目录
前言
我们知道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());
}
}
总结
总结发现七大设计原则离不开一个字"分",由"分"字联想到代理,再联想到网络接口,再联想到我们平常的生活和学习,遇到一件难事,拆"分"它为一件件小事,其中某几件事需要请求朋友(可理解为代理、接口)帮助。
-----结束语: 如果做某件事可以让你快乐,那只管放手去做就好