目录
开闭原则
概念
对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效
果。简言之,是为了使程序的扩展性好,易于维护和升级。
想要达到这样的效果,我们需要使用接口和抽象类。
因为抽象灵活性好,适应性广,只要抽象的合理,可以基本保持软件架构的稳定。而软件中易变的细节可以从抽象派生来的实现类来进行扩展,当软件需要发生变化时,只需要根据需求重新派生一个实现类来扩展就可以了。
代码示例
项目结构:
AbstractEcology.java:
package main.java.com.design_principles.demo1;
/**
* @version:
* @author: 零乘亿
* @description:
* @date: 2021/9/22 10:48
**/
public abstract class AbstractEcology {
public abstract void display();
}
DefaultEcology.java
package main.java.com.design_principles.demo1;
/**
* @version: v1.0
* @author: 零乘亿
* @description: 默认皮肤类
* @date: 2021/9/22 10:49
**/
public class DefaultEcology extends AbstractEcology{
@Override
public void display() {
System.out.println("地球生态");
}
}
MarsEcology .java:
package main.java.com.design_principles.demo1;
/**
* @version:
* @author: 零乘亿
* @description: 地球皮肤
* @date: 2021/9/22 10:51
**/
public class MarsEcology extends AbstractEcology{
@Override
public void display() {
System.out.println("火星生态");
}
}
Star .java:
package main.java.com.design_principles.demo1;
/**
* @version:
* @author: 零乘亿
* @description: 星球
* @date: 2021/9/22 10:59
**/
public class Star {
private AbstractEcology ecology;
public void setEcology(AbstractEcology ecology) {
this.ecology = ecology;
}
public void display(){
ecology.display();
}
}
Manufacturer.java
package main.java.com.design_principles.demo1;
/**
* @version:
* @author: 零乘亿
* @description: 星球制造者
* @date: 2021/9/22 11:03
**/
public class Manufacturer {
public static void main(String[] args) {
//1、创建星球对象
Star star = new Star();
//2、创建默认生态对象
// DefaultEcology defaultEcology = new DefaultEcology();
MarsEcology marsEcology = new MarsEcology();
//3、将生态设置到星球中
star.setEcology(marsEcology);
//4、显示生态
star.display();
}
}
代码解析
AbstractEcology:定义了一个抽象类表示生态,后面所有派生出的生态类都要集成这个抽象类。
DefaultEcology:默认的生态,要继承AbstractEcology类。
MarsEcology :火星生态,要继承AbstractEcology类。
Star:星球类,聚合AbstractEcology。
Manufacturer:制造者类,用于测试。
目前代码中星球的生态我设置的是火星,如果有需要将生态换成其它的星球生态,例如木星,水星,天王星等。
只需要再新建一个类继承公共的抽象类并设置对应的属性,然后将星球这个对象中的ecology属性修改为对应的生态即可。
依照开闭原则不会去修改原有存在的类,这就是对扩展开放,对修改关闭。
小结
结个屁屁,还没时间抓紧给我出!
里氏代换原则
概念
里氏代换原则:任何基类可以出现的地方,子类一定可以出现。
通俗理解:子类可以扩展父类的功能,但不能改变父类原有的功能。
换句话说,子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。
——黑马程序员
为什么说尽量不要重写父类的方法?
如果通过重写父类的方法来完成新的功能,这样写起来虽然简单,但是整个继承体系的可复用性会比较
差,特别是运用多态比较频繁时,程序运行出错的概率会非常大。——黑马程序员
通俗来说:之所以是父类就是它一定是抽取了子类的共同的特征的,在父类中已经定义的方法在子类中应该要直接复用,若子类还去重写,那么在父类定义的那个方法就没有意义了。若是规则规定要子类自己去实现那个方法,那么应该写成抽象方法,不去实现。
代码示例
经典案例说明:正方形不是长方形
项目结构:
Rectangle.java:
package main.java.com.learn.principles.demo2.before;
/**
* @version:
* @author: 零乘亿
* @description: 长方形类
* @date: 2021/9/24 18:51
**/
public class Rectangle {
private double length;
private double width;
public void setLength(double length) {
this.length = length;
}
public void setWidth(double width) {
this.width = width;
}
public double getLength() {
return length;
}
public double getWidth() {
return width;
}
}
Square.java:
package main.java.com.learn.principles.demo2.before;
/**
* @version:
* @author: 零乘亿
* @description: 正方形类
* @date: 2021/9/24 18:52
**/
public class Square extends Rectangle{
@Override
public void setLength(double length) {
super.setLength(length);
super.setWidth(length);
}
@Override
public void setWidth(double width) {
super.setWidth(width);
super.setLength(width);
}
}
RectangleDemo.java:
package main.java.com.learn.principles.demo2.before;
/**
* @version:
* @author: 零乘亿
* @description:
* @date: 2021/9/24 18:54
**/
public class RectangleDemo {
public static void main(String[] args) {
Rectangle r = new Rectangle();
r.setWidth(10);
r.setLength(20);
//调用resize方法进行扩宽
resize(r);
printLengthAndWidth(r);
System.out.println("=============================");
Square s = new Square();
s.setLength(10);
//调用resize方法进行扩宽
//resize(s);
printLengthAndWidth(s);
}
//扩宽方法
public static void resize(Rectangle rectangle) {
//判断如果比长小,进行扩宽地操作
while (rectangle.getWidth() <= rectangle.getLength()){
rectangle.setWidth(rectangle.getWidth()+1);
}
}
//打印长和宽
public static void printLengthAndWidth(Rectangle rectangle){
System.out.println("宽为:"+rectangle.getWidth());
System.out.println("长为:"+rectangle.getLength());
}
}
代码解析
首先新建Rectangle且有长宽两个属性,并且让Square继承Rectangle类。
当我们将正方形这个对象当作正常的长方形对象传入扩宽函数中时,程序便会陷入死循环,因为长与宽一直在同步增加。
如果不明白查看一下resize方法与正方形中set方法的实现就能够看明白。
得出结论:在resize方法中,Rectangle类型的参数是不能被Square类型的参数所代替,如果进行了替换就得不到预期结果
因此,Square类和Rectangle类之间的继承关系违反了里氏代换原则,它们之间的继承关系不成立,正方形不是长方形。——黑马程序员
使用接口的方式将其改进成满足里氏代换原则
代码改进
项目结构:
新建接口类-Quadrilateral .java:
package main.java.com.learn.principles.demo2.after;
/**
* @version:
* @author: 零乘亿
* @description: 四边形接口
* @date: 2021/9/24 19:18
**/
public interface Quadrilateral {
//获取长
double getLength();
//获取宽
double getWidth();
}
新建Square.java:
package main.java.com.learn.principles.demo2.after;
/**
* @version:
* @author: 零乘亿
* @description: 正方形类
* @date: 2021/9/24 19:25
**/
public class Square implements Quadrilateral{
private double side;
public double getSide() {
return side;
}
public void setSide(double side) {
this.side = side;
}
@Override
public double getLength() {
return side;
}
@Override
public double getWidth() {
return side;
}
}
新建Rectangle.java:
package main.java.com.learn.principles.demo2.after;
/**
* @version:
* @author: 零乘亿
* @description: 长方形类
* @date: 2021/9/24 19:29
**/
public class Rectangle implements Quadrilateral{
private double length;
private double width;
public void setLength(double length) {
this.length = length;
}
public void setWidth(double width) {
this.width = width;
}
@Override
public double getLength() {
return length;
}
@Override
public double getWidth() {
return width;
}
}
新建RectangleDemo.java:
package main.java.com.learn.principles.demo2.after;
import main.java.com.learn.principles.demo2.after.Rectangle;
import main.java.com.learn.principles.demo2.after.Square;
/**
* @version:
* @author: 零乘亿
* @description:
* @date: 2021/9/24 18:54
**/
public class RectangleDemo {
public static void main(String[] args) {
Rectangle r = new Rectangle();
r.setWidth(10);
r.setLength(20);
//调用resize方法进行扩宽
resize(r);
printLengthAndWidth(r);
System.out.println("=============================");
Square s = new Square();
s.setSide(10);
printLengthAndWidth(s);
}
//扩宽方法
public static void resize(Rectangle rectangle) {
//判断如果比长小,进行扩宽地操作
while (rectangle.getWidth() <= rectangle.getLength()){
rectangle.setWidth(rectangle.getWidth()+1);
}
}
//打印长和宽
public static void printLengthAndWidth(Quadrilateral quadrilateral){
System.out.println("宽为:"+quadrilateral.getWidth());
System.out.println("长为:"+quadrilateral.getLength());
}
}
代码解析
将正方形和长方形提取出一个公共的接口,正方形与长方形类去实现这个接口。
在扩宽方法中传入的参数设为长方形类,此时的正方形与长方形之间没有继承关系,所以正方形对象便无法再进行扩宽的操作,而在打印的方法中,参入的参数是正方形与长方形实现的接口,所以两者都能够正常使用。
RectangleDemo.java:
小结
结个屁屁,还没时间抓紧给我出!
依赖倒转原则
概念
高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。
简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。——黑马程序员
依赖倒转原则就很好的实现了开闭原则的要求。
代码示例
项目结构:
创建demo3文件夹,在里面再新建一个before文件夹
新建IntelCpu.java:
package main.java.com.learn.principles.demo3.before;
/**
* @version:
* @author: 零乘亿
* @description: 英特尔cpu类
* @date: 2021/9/24 19:58
**/
public class IntelCpu {
public void run(){
System.out.println("使用因特尔处理器");
}
}
新建KingstonMemory.java:
package main.java.com.learn.principles.demo3.before;
/**
* @version:
* @author: 零乘亿
* @description: 金士顿内存条类
* @date: 2021/9/24 19:59
**/
public class KingstonMemory {
public void save(){
System.out.println("使用金士顿内存条");
}
}
新建XiJieHardDisk.java:
package main.java.com.learn.principles.demo3.before;
/**
* @version:
* @author: 零乘亿
* @description: 希捷硬盘
* @date: 2021/9/24 19:56
**/
public class XiJieHardDisk {
//存储数据的方法
public void save(String data){
System.out.println("使用希捷硬盘存储数据为:" + data);
}
//获取数据的方法
public String get(){
System.out.println("使用希捷硬盘取数据");
return "数据";
}
}
新建Computer.java:
package main.java.com.learn.principles.demo3.before;
/**
* @version:
* @author: 零乘亿
* @description: 电脑类
* @date: 2021/9/24 20:15
**/
public class Computer {
private XiJieHardDisk hardDisk;
private IntelCpu cpu;
private KingstonMemory memory;
public void setHardDisk(XiJieHardDisk hardDisk) {
this.hardDisk = hardDisk;
}
public void setCpu(IntelCpu cpu) {
this.cpu = cpu;
}
public void setMemory(KingstonMemory memory) {
this.memory = memory;
}
public XiJieHardDisk getHardDisk() {
return hardDisk;
}
public IntelCpu getCpu() {
return cpu;
}
public KingstonMemory getMemory() {
return memory;
}
public void run(){
System.out.println("运行计算机");
String data = hardDisk.get();
System.out.println("从硬盘上获取的数据是:" + data);
cpu.run();
memory.save();
}
}
新建ComputerDemo.java:
package main.java.com.learn.principles.demo3.before;
/**
* @version:
* @author: 零乘亿
* @description:
* @date: 2021/9/24 20:19
**/
public class ComputerDemo {
public static void main(String[] args) {
//创建组件对象
XiJieHardDisk hardDisk = new XiJieHardDisk();
IntelCpu cpu = new IntelCpu();
KingstonMemory memory = new KingstonMemory();
//创建计算机对象
Computer c = new Computer();
//组装计算机
c.setCpu(cpu);
c.setHardDisk(hardDisk);
c.setMemory(memory);
//运行计算机
c.run();
}
}
代码解析
创建了电脑配件Cpu,Hard,Memory组件,再创建Computer类将他们都组装起来,最后再使用一个测试类进行测试。
但是在以上的代码中存在一个问题,当电脑的Cpu要进行更换时,就需要求改动Cpu类中的代码,这不符合依赖倒转原则,我们使用接口来解决这样的问题。
代码改进
目录结构:
新建接口类Cpu.java:
package main.java.com.learn.principles.demo3.after;
/**
* @version:
* @author: 零乘亿
* @description:
* @date: 2021/9/24 22:19
**/
public interface Cpu {
//运行cpu
public void run();
}
新建类IntelCpu.java:
package main.java.com.learn.principles.demo3.after;
/**
* @version:
* @author: 零乘亿
* @description: IntelCpu
* @date: 2021/9/24 19:58
**/
public class IntelCpu implements Cpu{
public void run(){
System.out.println("使用因特尔处理器");
}
}
新建接口Memory.java:
package main.java.com.learn.principles.demo3.after;
/**
* @version:
* @author: 零乘亿
* @description: 内存条接口
* @date: 2021/9/24 22:22
**/
public interface Memory {
//存储方法
public void save();
}
新建类KingstonMemory.java:
package main.java.com.learn.principles.demo3.after;
/**
* @version:
* @author: 零乘亿
* @description: 金士顿内存条类
* @date: 2021/9/24 19:59
**/
public class KingstonMemory implements Memory{
public void save(){
System.out.println("使用金士顿内存条");
}
}
新建接口HardDisk.java:
package main.java.com.learn.principles.demo3.after;
/**
* @version:
* @author: 零乘亿
* @description: 硬盘接口
* @date: 2021/9/24 20:24
**/
public interface HardDisk {
//存储数据
public void save(String data);
//获取数据
public String get();
}
新建类XiJieHardDisk.java:
package main.java.com.learn.principles.demo3.after;
/**
* @version:
* @author: 零乘亿
* @description: 希捷硬盘
* @date: 2021/9/24 19:56
**/
public class XiJieHardDisk implements HardDisk{
//存储数据的方法
public void save(String data){
System.out.println("使用希捷硬盘存储数据为:" + data);
}
//获取数据的方法
public String get(){
System.out.println("使用希捷硬盘取数据");
return "数据";
}
}
新建类Computer.java:
package main.java.com.learn.principles.demo3.after;
/**
* @version:
* @author: 零乘亿
* @description:
* @date: 2021/9/24 22:23
**/
public class Computer {
private HardDisk hardDisk;
private Cpu cpu;
private Memory memory;
public void setHardDisk(HardDisk hardDisk) {
this.hardDisk = hardDisk;
}
public void setCpu(Cpu cpu) {
this.cpu = cpu;
}
public void setMemory(Memory memory) {
this.memory = memory;
}
public HardDisk getHardDisk() {
return hardDisk;
}
public Cpu getCpu() {
return cpu;
}
public Memory getMemory() {
return memory;
}
//运行计算机
public void run(){
System.out.println("运行计算机");
String data = hardDisk.get();
System.out.println("从硬盘上获取的数据是:" + data);
cpu.run();
memory.save();
}
}
新建类ComputerDemo.java:
package main.java.com.learn.principles.demo3.after;
/**
* @version:
* @author: 零乘亿
* @description:测试类
* @date: 2021/9/24 22:24
**/
public class ComputerDemo {
public static void main(String[] args) {
//创建计算机的组件对象
HardDisk hardDisk = new XiJieHardDisk();
Cpu cpu = new IntelCpu();
Memory memory = new KingstonMemory();
//创建计算机对象
Computer c = new Computer();
//组装计算机
c.setCpu(cpu);
c.setHardDisk(hardDisk);
c.setMemory(memory);
//运行计算机
c.run();
}
}
代码解析
将上一个版本的组件Cpu,Hard,Memory,建立了对应的接口,然后在分别建立他们对应的实现类。
这样实现之后,当cpu要进行组件更改时,只要在多新建一个对应的类然后实现想对应的接口,然后在Computer类中将对应的组件更换即可完成,不需要再组件中修改代码。
小结
结个屁屁,还没时间抓紧给我出!
接口隔离原则
概念
客户端不应该被迫依赖于它不使用的方法;一个类对另一个类的依赖应该建立在最小的接口上。——黑马程序员
一共类继承的接口,就会拥有该接口中定义的所有的方法,而继承到的方法一定是该类都需要用到的方法。
如果该类继承的这个接口中的所有的方法不是这个会都使用到的方法,那么就没有达到接口隔离原则。
图例说明
当只有一共接口时,但凡实现了该接口的类都会有以上三种方法,但是这个三种方法并非是所有实现该接口的类都会使用到的,这就不符合接口隔离原则的思想。
这种方式就体现了接口隔离原则的思想,将每个方法都设置为一个接口,实现类要实现哪个方法就去继承哪个接口,不会造成继承了接口,实现了方法,但却用不到的情况。
代码示例
暂时省略,以后有时间一定加上。
迪米特法则
概念
只和你的直接朋友交谈,不跟“陌生人”说话。
如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。
迪米特法则中的“朋友”是指:当前对象本身、当前对象的成员对象、当前对象所创建的对象、当前对象的方法参数等,这些对象同当前对象存在关联、聚合或组合关系,可以直接访问这些对象的方法。
——黑马程序员
举个例子:
一个公司需要外包做一个程序,该公司找的是软件的外包公司,做软件的是程序员,但是该公司的需求是由外包公司提供给程序员的,减少了该公司与程序员的沟通。
或
买房时一般是跟房屋中介谈而不是直接跟房主谈。
图例说明
代码示例
合成复用原则
合成复用原则是指:尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。
——黑马程序员
继承方式存在的缺点:
缺点 | 原因 |
---|---|
破坏类的封装性 | 将父类的细节暴露给子类,称为“白箱”复用 |
父子类耦合度高 | 父类的任何改变都会影响到它的子类 |
限制了复用的灵活性 | 继承是静态的,在编译时就确定,运行时不可变化 |
组合和聚合复用的优点:
优点 | 原因 |
---|---|
维持了类的封装性 | 成分对象的内部细节是新对象看不见的 ,称为“黑箱”复用。 |
降低耦合 | 类的成员位置声明抽象(接口) |
复用的灵活性高 | 在运行时实现声明类成员的子类(实现) |
图例说明
继承的方式:
当汽车新增加一种动力源时,就要多出三个子类。
如果再新增一种动力源,那么子类又会继续增加三个,就会出现子类非常多的情况。
将汽车的颜色作为一个接口,以属性的方式组合在类Car中,当新增加一个新动力汽车时就不会说要新增三个类,而是只需要新增一个类就足够了。
代码示例
思维导图
代码示例找时间补上,一定,要赶着软考,笔记做的匆忙。