软件设计的七大原则
在软件开发中,为了提高软件系统的可维护性和可复用性,增加软件的可扩展性和灵活性,程序员要尽量根据 7 条原则来开发程序,从而提高软件开发效率、节约软件开发成本和维护成本。我来依次来总结这 7 条原则。
这 7 种设计原则是软件设计模式必须尽量遵循的原则,各种原则要求的侧重点不同。其中,
- 开闭原则是总纲,它告诉我们要对扩展开放,对修改关闭;
- 单一职责原则告诉我们实现类要职责单一;
- 依赖倒置原则告诉我们要面向接口编程;
- 接口隔离原则告诉我们在设计接口的时候要精简单一;
- 迪米特法则告诉我们要降低耦合度;
- 里氏替换原则告诉我们不要破坏继承体系;
- 合成复用原则告诉我们要优先使用组合或者聚合关系复用,少用继承关系复用。
开闭原则
当应用的需求改变时,在不修改软件实体的源代码或者二进制代码的前提下,可以扩展模块的功能,使其满足新的需求
创建了一个汽车对象
class Car {private String band;private String color;private float price;public String getBand() {return band;}public void setBand(String band) {this.band = band;}public String getColor() {return color;}public void setColor(String color) {this.color = color;}public float getPrice() {return price;}public void setPrice(float price) {this.price = price;}@Overridepublic String toString() {return "Car{" +"band='" + band + '\'' +", color='" + color + '\'' +", price=" + price +'}';}}public static void main(String[] args) {Car car = new Car();car.setBand("bm");car.setColor("red");car.setPrice(13.3f);System.out.println(car.toString());}
当变化来临时,例如汽车的价格现在需要打折(打8折),这时,我们在Car的源代码中修改,就违反了开闭原则
开发时,我们应该要去考虑变化的需求,属性会在任何时刻都有可能产生变化
进行扩展
//进行扩展class DiscountCar extends Car{@Overridepublic void setPrice(float price) {super.setPrice(price*0.08f);}}public static void main(String[] args) {//===使用向上转型时,方法的调用只和new的对象有关Car car = new DiscountCar();car.setBand("bm");car.setColor("red");car.setPrice(130000f);System.out.println(car.toString());}
开闭原则应该遵循应用场景去考虑,如果源代码就是你自己写的,而且需求是稳定的,那么,直接修改源代码也是一个简单的做法,但当源代码是别人的代码或架构是,我们就要去符合开闭原则,防止破坏结构的完整性!
单一职责
这里的职责是指类变化的原因,单一职责原则规定一个类应该有且仅有一个引起它变化的原因,否则类应该被拆分
每个类或每个方法每个框架只做一件事
统计文本文件中有多少个单词
//==============只负责根据路径加载文件public String loadFile(String path) throws IOException {Reader in = new FileReader(path);BufferedReader br = new BufferedReader(in);String line = null;StringBuilder sb = new StringBuilder("");while ((line = br.readLine()) != null) {sb.append(line);sb.append("");}br.close();return sb.toString();}//=========只负责字符串根据正则分割public int Textlength(String sb, String regex){String [] words = sb.split(regex);return words.length;}public static void main(String[] args) throws IOException {String str = loadFile("F:\\1.txt");String regex ="[^a-zA-Z]+";System.out.println(Textlength(str,regex));}
依赖倒置原则
高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象其核心思想是:要面向接口编程,不要面向实现编程。
人喂养动物
static class Person{public void feed(Dog dog){dog.eat();}}static class Dog{public void eat() {System.out.println("狗啃骨头");}}public static void main(String[] args) {Person p = new Person();Dog d = new Dog();p.feed(d);}
在上面的代码中,人要喂狗,依赖于有一条狗,人作为上层依赖于下层,这样有什么坏处呢?
坏处是,当变化来临时,比如,人又养了一只猫,那么上层人这个类当中,就必须在添加喂猫的方法,每当下层变动时,上层也会跟着变动,而我们希望下层变动时,上层不会跟着改变(上层直接依赖了下层)
他们都应该依赖于抽象
interface Animal{void eat();}static class Person{public void feed(Animal animal){animal.eat();}}static class Dog implements Animal{public void eat() {System.out.println("狗啃骨头");}}public static void main(String[] args) {Person p = new Person();Dog d = new Dog();p.feed(d);}
依赖倒转原则就是指:代码要依赖于抽象的类,而不要依赖于具体的类;要针对接口或抽象类编程,而不是针对具体类编程。通过面向接口编程,抽象不应该依赖于细节,细节应该依赖于抽象(倒过来了)。
接口隔离原则
接口隔离原则要求程序员尽量将臃肿庞大的接口拆分成更小的和更具体的接口,让接口中只包含客户感兴趣的方法。
设计接口时,接口的抽象应该是有意义的
反例: 动物接口中定义的方法并不是被所有动物需要的
interface Animal{void eat();void fly();void swim();}class Bird implements Animal{@Overridepublic void eat() {System.out.println("吃");}@Overridepublic void fly() {System.out.println("飞");}//======鸟不会游泳,并不需要实现@Overridepublic void swim() {System.out.println("游泳");}}
正例:接口抽象出有意义的层级,供需要的类去实现
interface Flyable{void fly();}interface Swimable{void swim();}interface Eatable{void eat();}class Bird implements Flyable,Eatable{.....}class Dog implements Swimable,Eatable{.....}
客户端不应该依赖那些它不需要的接口。
一旦一个接口太大,则需要将它分割成一些更细小的接口,使用该接口的客户端仅需知道与之相关的方法即可
迪米特法则
只与你的直接朋友交谈,不跟“陌生人”说话。其含义是:如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。
(最少知道原则)封装,只和朋友通信
什么是朋友?
1.类中的字段
2.方法的返回值
3.方法的参数
4.方法中的实例对象
5.对象本身
6.集合中的泛型
反例:关闭电脑的流程
class Computers{public void closeFile(){System.out.println("关闭文件");}public void closeScreen(){System.out.println("关闭屏幕");}public void powerOff(){System.out.println("断电");}}class Person{private Computers computers;public void offComputers(){computers.closeFile();computers.closeScreen();computers.powerOff();}}
当用户关闭电脑时,需要调用计算机的各个方法,但是这些方法的细节太多了,会出现用户流程出错,遗漏调用等等,对于用户来言,他只需要知道关机按钮就够了
正例:封装细节,提供接口
class Computers{public void closeFile(){System.out.println("关闭文件");}public void closeScreen(){System.out.println("关闭屏幕");}public void powerOff(){System.out.println("断电");}public void offComputers(){closeFile();closeScreen();powerOff();}}class Person{private Computers computers;public void offComputers(){computers.offComputers();}}
里氏替换原则
子类可以扩展父类的功能,但不能改变父类原有的功能。也就是说:子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。关于里氏替换原则的例子,最有名的是“正方形不是长方形”。
在任何使用父类对象的地方,替换为子类对象以后,程序不会有问题出现.
里氏替换原则在“几维鸟不是鸟”实例中的应用
package principle;public class LSPtest{public static void main(String[] args){Bird bird1=new Swallow();Bird bird2=new BrownKiwi();bird1.setSpeed(120);bird2.setSpeed(120);System.out.println("如果飞行300公里:");try{System.out.println("燕子将飞行"+bird1.getFlyTime(300)+"小时.");System.out.println("几维鸟将飞行"+bird2.getFlyTime(300)+"小时。");}catch(Exception err){System.out.println("发生错误了!");}}}//鸟类class Bird{double flySpeed;public void setSpeed(double speed){flySpeed=speed;}public double getFlyTime(double distance){return(distance/flySpeed);}}//燕子类class Swallow extends Bird{}//几维鸟类class BrownKiwi extends Bird{public void setSpeed(double speed){flySpeed=0;}}
合成复用原则
合成复用原则(Composite Reuse Principle,CRP)又叫组合/聚合复用原则(Composition/Aggregate Reuse Principle,CARP)。它要求在软件复用时,要尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。
统计集合添加元素的次数
反例:继承hashset,重写父类add方法,每次add元素时count+1;
class Counter extends HashSet {private int count =0;public boolean add(int i) {count++;return super.add(i);}public int getCount(){return count;}}public static void main(String[] args) {Counter c = new Counter();c.add(1);c.add(2);c.add(3);System.out.println(c.getCount());}
这样写会出现在hashset方法中,不只是add可以添加元素的问题,还有其他方式可以添加,如果是其他方式添加的,count就不会累加,这样是有问题的
@Overridepublic boolean addAll(Collection c) {count=count+c.size();return super.addAll(c);}
假如我们继续重写addAll方法添加相应的判断时,又会出现新的问题,我们的count并没有正确的累计,因为在HashSet的源码addAll方法中,回调了add方法,并没有解决需求
那我们不重写addAll,反正它会回调add,完成计数,就没有问题了吗?
其实并不能解决问题,hashset的源码我们不能保证永远不会更改,假如在下一个版本中,hashset的作者更改了addAll方法,那么我们的功能也会不能正常实现了!
当继承的父类作者不是我们自己的时候,我们没有办法保证父类代码不会变更,假如我们继承了这个父类,那么我们最好是只去复用父类的代码,避免去重写或新建方法,防止源码结构变更带来的打击
也就是说,在我们需要重用代码,并且重用的代码作者并不是我们自己的时候,我们要采用组合的方式。
正例:组合优于继承
static class Counter {HashSet hashSet = new HashSet();private int count =0;public boolean add(int i) {count++;return hashSet.add(i);}public boolean addAll(Collection c) {count=count+c.size();return hashSet.addAll(c);}public int getCount(){return count;}}public static void main(String[] args) {Counter c = new Counter();c.add(1);c.add(2);c.add(3);System.out.println(c.getCount());}
这样来写,我们类中的add和addAll方法跟HashSet中的add和addAll方法的不在有关系,也能解决这个问题
设计模式
工厂模式
简单工厂模式
正例:创建产品接口,具体产品实现,工厂类负责生产,使用时找到工厂类拿到对应产品
class Test {interface Food {void eat();}static class Bun implements Food {@Overridepublic void eat() {System.out.println("包子");}}static class SteameDread implements Food {@Overridepublic void eat() {System.out.println("馒头");}}static class FoodFactory {public static Food getFood(int n) {Food food = null;switch (n) {case 1:food = new Bun();break;case 2:food = new SteameDread();break;}return food;}}public static void main(String[] args) {FoodFactory.getFood(1).eat();;}}
优点
1.把具体产品的类型,从客户端代码中,解耦出来。
2.服务器端,如果修改了具体产品的类名,客户端也不知道!这便符合了“面向接口编程的思想
缺点
1.客户端不得不死记硬背那些常量与具体产品的映射关系,比如:1—汉堡包,2—馒头
2.如何具体产品特别多,则简单工厂,就会变得十分臃肿。比如有100个具体产品,则需要在简单工厂的switch写出100个case!3.最重要的是,变化来了:客户端需要扩展具体产品的时候,势必要修改简单工厂中的代码,这样便违反了”开闭原则
工厂方法模式
class Test {interface Food {void eat();}static class Bun implements Food {@Overridepublic void eat() {System.out.println("包子");}}static class SteameDread implements Food {@Overridepublic void eat() {System.out.println("馒头");}}//工厂static interface FoodFactory{Food getFood();}static class BunFactory implements FoodFactory{@Overridepublic Food getFood() {return new Bun();}}static class SteameDreadFactory implements FoodFactory{@Overridepublic Food getFood() {return new SteameDread();}}static class Business {public static void test(FoodFactory foodFactory){Food food = foodFactory.getFood();food.eat();}}public static void main(String[] args) {FoodFactory ff = new BunFactory();ff.getFood();//Business.test(new SteameDreadFactory());}}
优点:
1.仍然具有简单工厂的忧点,赈务器端修改了具体产品的类名以后,客户端不知道!
2.当客户端需要扩展一个新的产品时,不需要修改作者原来的代码,只是扩展一个新的工厂而已!
缺点:
如果有多个品等级,那么工厂类的数量,就会爆炸式增长!
抽象工厂模式
和抽象方法差不多,不过工厂中会依赖多个产品
6mm工厂只能生产6mm螺丝6mm螺母
class Test {interface Food {void eat();}interface Drink {void drink();}static class DoughSticks implements Food {@Overridepublic void eat() {System.out.println("油条");}}static class SteamedBuns implements Food {@Overridepublic void eat() {System.out.println("馒头");}}static class SoybeanMilk implements Drink {@Overridepublic void drink() {System.out.println("豆浆");}}static class Milk implements Drink {@Overridepublic void drink() {System.out.println("牛奶");}}interface Factory {Food getFood();Drink getDrink();}static class DoughSticksFactory implements Factory {@Overridepublic Food getFood() {return new DoughSticks();}@Overridepublic Drink getDrink() {return new SoybeanMilk();}}static class SteamedBunsFactory implements Factory {@Overridepublic Food getFood() {return new SteamedBuns();}@Overridepublic Drink getDrink() {return new Milk();}}static class Business {public void test(Factory factory) {Food food = factory.getFood();food.eat();Drink drink = factory.getDrink();drink.drink();}}public static void main(String[] args) {Business business = new Business();business.test(new DoughSticksFactory());System.out.println();business.test(new SteamedBunsFactory());}}/**油条豆浆馒头牛奶*/
优点:
1、仍然有简单工厂和工厂方法的优点
2.更重要的是,抽象工厂把工厂类的数量减少了!无论有多少个产品等级,工厂就一套。
解释:
抽象工厂中,可以生产多个产品,这多个产品之间.必须有内在联系。
同一个工厂中的产品都属于同一个产品簇!!不能把不同产品簇中的产品混合到一个象工厂的实现类中。

产品簇和产品等级多看看,容易搞混,如果增加个京东工厂(工厂类)叫多了个产品簇,如果增加个电器-空调(工厂类中的方法)叫多了个产品等级
缺点:
1.当,产品等级发生变化时(增如产品等价、删除产品等价〕,都要引起所有以前工厂代码的修改,这就违反了””开闭原则!
结论:
当产品等级比较固定时,可以考虑使用抽象工厂,|如果产品等级经常变化,则不建议使用抽象工厂。
策略模式
一个类的行为可以在运行时更改
public class ZhaoYun {public static interface IStrategy {//每个锦囊妙计都是一个可执行的算法public void operate();}public static class BackDoor implements IStrategy {public void operate() {System.out.println("找乔国老帮忙,让吴国太给孙权施加压力");}}public static class GivenGreenLight implements IStrategy {public void operate() {System.out.println("求吴国太开个绿灯,放行!");}}public static class BlockEnemy implements IStrategy {public void operate() {System.out.println("孙夫人断后,挡住追兵");}}public static class Context {//构造函数,你要使用那个妙计private IStrategy strategy;public void setStrategy(IStrategy strategy) {this.strategy = strategy;}//使用计谋了,看我出招了public void operate() {this.strategy.operate();}}public static void main(String[] args) {;System.out.println("-----------刚刚到吴国的时候拆第一个-------------");Context context = new Context();context.setStrategy(new BackDoor());//拿到妙计context.operate(); //拆开执行System.out.println("-----------刘备乐不思蜀了,拆第二个了-------------");context.setStrategy(new GivenGreenLight());//拿到妙计context.operate(); //执行了第二个锦囊了System.out.println("-----------孙权的小兵追了,咋办?拆第三个------------ - ");context.setStrategy(new BlockEnemy());//拿到妙计context.operate(); //孙夫人退兵/**问题来了:赵云实际不知道是那个策略呀,他只知道拆第一个锦囊,*而不知道是BackDoor这个妙计,咋办? 似乎这个策略模式已经把计谋名称写出来了** 错!BackDoor、GivenGreenLight、BlockEnemy只是一个代码,你写成first、second、third,没人会说你错!** 策略模式的好处就是:体现了高内聚低耦合的特性呀,缺点嘛,这个那个,我回去再查查*/}}

静态代理
一个接口,多个实现类,代理类要需要实现接口(通过构造器接受实现类),在代理类方法中调用具体的实现类方法
什么是代理模式呢?我很忙,忙的没空理你,那你要找我呢就先找我的代理人吧,那代理人总要知道 被代理人能做哪些事情不能做哪些事情吧,那就是两个人具备同一个接口,代理人虽然不能干活,但是被 代理的人能干活呀。

KindWomen
public interface KindWomen {//这种类型的女人能做什么事情呢?void makeEyesWithMan(); //抛媚眼void happyWithMan(); //happy what? You know that!}
PanJinLian
public class PanJinLian implements KindWomen{@Overridepublic void makeEyesWithMan() {System.out.println("潘金莲抛媚眼");}@Overridepublic void happyWithMan() {System.out.println("潘金莲在和男人做那个.....");}}
JiaShi
public class JiaShi implements KindWomen{@Overridepublic void makeEyesWithMan() {System.out.println("贾氏抛媚眼");}@Overridepublic void happyWithMan() {System.out.println("贾氏正在Happy中......");}}
WangPo(代理类)
public class WangPo implements KindWomen {private KindWomen kindWomen;public WangPo(){ //默认的话,是潘金莲的代理this.kindWomen = new PanJinLian();}//她可以是KindWomen的任何一个女人的代理,只要你是这一类型public WangPo(KindWomen kindWomen){this.kindWomen = kindWomen;}public void happyWithMan() {this.kindWomen.happyWithMan(); //自己老了,干不了,可以让年轻的代替}public void makeEyesWithMan() {this.kindWomen.makeEyesWithMan(); //王婆这么大年龄了,谁看她抛媚眼?!}}
XiMenQing(测试类)
public class XiMenQing {/** 水浒里是这样写的:西门庆被潘金莲用竹竿敲了一下难道,痴迷了,* 被王婆看到了, 就开始撮合两人好事,王婆作为潘金莲的代理人* 收了不少好处费,那我们假设一下:* 如果没有王婆在中间牵线,这两个不要脸的能成吗?难说的很!*/public static void main(String[] args) {//把王婆叫出来WangPo wangPo = new WangPo();//然后西门庆就说,我要和潘金莲happy,然后王婆就安排了西门庆丢筷子的那出戏:wangPo.makeEyesWithMan(); //看到没,虽然表面上时王婆在做,实际上爽的是潘金莲wangPo.happyWithMan();System.out.println("\n");wangPo = new WangPo(new JiaShi());// 改编一下历史,贾氏被西门庆勾走:wangPo.makeEyesWithMan(); //让王婆作为贾氏的代理人wangPo.happyWithMan();}}
模板方法模式
将不会改变的地方变为模板,而会发生改变的部分代码变成抽象的,这样调用它的人负责实现,来实现它需要的效果
abstract class Test {public void templent() {long start = System.currentTimeMillis();code();long end = System.currentTimeMillis();System.out.println(end - start);}abstract void code();}class ArrayTest extends Test {@Overridevoid code() {List<Integer> list = new ArrayList<>();for (int i = 0; i < 10000; i++) {list.add(0, 1);}}}class AppTest {public static void main(String[] args) {Test test = new ArrayTest();test.templent();}}
建造者模式

// 电脑@Dataclass Computer {private String cpu;private String gpu;private String hd;private String memory;}// 电脑构建者interface ComputerBuilder {void buildCpu();void buildGpu();void buildMemory();void buildHd();Computer build();}class AdvanceComputerBuilder implements ComputerBuilder {Computer c = new Computer();@Overridepublic void buildCpu() {c.setCpu("i9-9700K");}@Overridepublic void buildGpu() {c.setGpu("3090");}@Overridepublic void buildMemory() {c.setMemory("32G");}@Overridepublic void buildHd() {c.setHd("2T固态");}@Overridepublic Computer build() {return c;}}class MiddleComputerBuilder implements ComputerBuilder {Computer c = new Computer();@Overridepublic void buildCpu() {c.setCpu("i7-5700HQ");}@Overridepublic void buildGpu() {c.setGpu("1080");}@Overridepublic void buildMemory() {c.setMemory("32G");}@Overridepublic void buildHd() {c.setHd("1T固态");}@Overridepublic Computer build() {return c;}}class LowComputerBuilder implements ComputerBuilder {Computer c = new Computer();@Overridepublic void buildCpu() {c.setCpu("i5-5400u");}@Overridepublic void buildGpu() {c.setGpu("950M");}@Overridepublic void buildMemory() {c.setMemory("4G");}@Overridepublic void buildHd() {c.setHd("1T机械");}@Overridepublic Computer build() {return c;}}//指挥者class Direct{public static Computer build(ComputerBuilder builder){builder.buildCpu();builder.buildGpu();builder.buildHd();builder.buildMemory();return builder.build();}}public class Client {public static void main(String[] args) {Computer c1 = Direct.build(new AdvanceComputerBuilder());System.out.println(c1);Computer c2 = Direct.build(new MiddleComputerBuilder());System.out.println(c2);Computer c3 = Direct.build(new LowComputerBuilder());System.out.println(c3);}}
输出
Computer(cpu=i9-9700K, gpu=3090, hd=2T固态, memory=32G)Computer(cpu=i7-5700HQ, gpu=1080, hd=1T固态, memory=32G)Computer(cpu=i5-5400u, gpu=950M, hd=1T机械, memory=4G)
优点:
1,创建对象的过程稳定不变的(因为有ComputerBuilder接口来稳定过程)
2.创建对象的过程只写了一次,没有重复代码(指挥者完成〕
3.当需要扩展指挥者的时候,不用修改之前的代码。这符合了开团原则
建造者与工厂模式的区别。
工厂模式只需一个简单的new. new出产品即可。
建造者更注重,在new出产品之后的。为产品属性赋值的过程! ! l
观察者模式
interface Observer {void update();}abstract class Subject {private List<Observer> observerList = new ArrayList<>();public void attach(Observer observer) {observerList.add(observer);}public void detach(Observer observer) {observerList.remove(observer);}public void notifyAllObserver() {for (Observer observer : observerList) {observer.update();}}public abstract void doSomething();}//矩形血条class RectangleBlood implements Observer {@Overridepublic void update() {System.out.println("收到消息----矩形血条");}}//圆形血条class CircleBlood implements Observer {@Overridepublic void update() {System.out.println("收到消息----圆形血条");}}class HeroSubject extends Subject {@Overridepublic void doSomething() {System.out.println("Hero做事情");super.notifyAllObserver();}}public class Client {public static void main(String[] args) {HeroSubject hero = new HeroSubject();RectangleBlood rectangleBlood = new RectangleBlood();CircleBlood circleBlood = new CircleBlood();hero.attach(rectangleBlood);hero.attach(circleBlood);hero.doSomething();}}
