【Java设计模式】软件设计七大原则
文章目录
软件设计原则的分类
- 开闭原则
- 依赖倒置原则
- 单一职责原则
- 接口隔离原则
- 迪米特法则(最少知道原则)
- 里氏替换原则
- 合成/复用原则(组合/复用原则)
在设计模式中会有这7中软件设计原则的体现,但是值得注意的是这7钟设计原则在设计模式中的使用是有取舍的,有的可能是完整的体现,也可能是部分地体现设计原则;有的可能使用了,有的可能没有使用等。
软件设计原则的学习应为软件设计模式的学习打好基础
开闭原则
定义:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。
核心思想:用抽象构建框架,用实现扩展细节
优点:提高软件系统的可复用性及可维护性
开闭原则是面向对象最基础的设计原则,它能够帮助我们开发稳定灵活的系统
下面来演示一下:
场景描述:现在要卖“雪花酥”小蛋糕大礼包,雪花酥小蛋糕大礼包的属性有编号,价格,口味。
首先定义一个接口:
package softwaredesign;
public interface ICake {
Integer getID();
String getTaste();
Double getPrice();
}
实现类
package softwaredesign;
public class SnowCake implements ICake {
private Integer id;
private String taste;
private Double price;
public SnowCake(Integer id, String taste, Double price) {
this.id = id;
this.taste = taste;
this.price = price;
}
@Override
public Integer getID() {
return this.id;
}
@Override
public String getTaste() {
return this.taste;
}
@Override
public Double getPrice() {
return this.price;
}
@Override
public String toString() {
return "SnowCake{" +
"id=" + id +
", taste='" + taste + '\'' +
", price=" + price +
'}';
}
}
测试:
package softwaredesign;
public class Test {
public static void main(String[] args) {
SnowCake sc = new SnowCake(100,"原味",55.0);
System.out.println(sc);
}
}
结果是正常的
转换成UML图:
从类结构图中 很容易类结构的信息
最近618 淘宝也搞活动了雪花酥要进行打8折了。
这个时候的开发要注意了 不要动接口 不要动接口 接口作为契约应该是可靠的,稳定的。
添加一个类
package softwaredesign;
public class SnowCakeDiscount extends SnowCake {
public SnowCakeDiscount(Integer id, String taste, Double price) {
super(id, taste, price);
}
@Override
public Integer getID() {
return super.getID();
}
@Override
public String getTaste() {
return super.getTaste();
}
@Override
public Double getPrice() {
return super.getPrice()*0.8;
}
@Override
public String toString() {
return "SnowCake{" +
"id=" + super.getID() +
", taste='" + super.getTaste()+ '\'' +
", price=" + super.getPrice()*0.8 +
'}';
}
}
测试
package softwaredesign;
public class Test {
public static void main(String[] args) {
SnowCake sc = new SnowCakeDiscount(100,"原味",55.0);
System.out.println(sc);
}
}
上述是UML图
依赖倒置原则(三种方式)
定义:高层模块不应该依赖低层模块,二者都应该依赖其抽象
抽象不应该依赖细节;细节应该依赖抽象
针对接口编程,不要针对实现编程
注意
每个类都尽量继承自接口或者抽象类(可以继承抽象类实现接口方法)
尽量不要从具体的类派生
尽量不要覆盖其基类的方法
优点:
可以减少类间的耦合性、提高系统稳定性,提高代码可读性和可维护性,可降低修改程序所造成的风险
下面来演示一下:
小明对淘宝上卖的鼠标 和 键盘都比较感兴趣
首先演示一个面向实现编程的例子
Buy类
package softwaredesign;
public class Buy {
public void buyMouse(){
System.out.println("小明在买鼠标");
}
public void buyKeyboard(){
System.out.println("小明在买键盘");
}
}
Test
package softwaredesign;
public class Test {
public static void main(String[] args) {
Buy buy = new Buy();
buy.buyMouse();
buy.buyKeyboard();
}
}
假如小明又想买 鼠标垫
在Buy类中添加
public void buyMousePad(){
System.out.println("小明在买键盘垫");
}
就是妥妥的面向实现编程
这个是与 依赖倒置 原则 是背道而驰的
高层次的模块不应该依赖低层次的模块
下面演示 满足依赖倒置原则的情况
创建一个Things接口
package softwaredesign;
public interface Things {
void buyThins();
}
继承接口的类:
package softwaredesign;
public class BuyMouse implements Things {
@Override
public void buyThins() {
System.out.println("小明正在买鼠标");
}
}
package softwaredesign;
public class BuyKeyboard implements Things {
@Override
public void buyThins() {
System.out.println("小明正在买键盘");
}
}
将继承接口的类联系起来
package softwaredesign;
public class Buy {
public void buy(Things things){
things.buyThins();
}
}
测试
package softwaredesign;
public class Test {
public static void main(String[] args) {
Buy b = new Buy();
b.buy(new BuyMouse());
b.buy(new BuyKeyboard());
}
}
打印结果与前面面向实现的结果是一样的
我们看一下类图:
如果有拓展的要买的东西 都和BuyMouse和BuyKeyboard平级了
具体的实现类 Buy类是不需要动的
也就是说我们面向接口编程, 不需要变动实现类
这里的Test测试类 和 Buy类是解耦的
当然也可以通过构造器注入 来进行Buy的实现
没有改动的
package softwaredesign;
public class Buy {
public void buy(Things things){
things.buyThins();
}
}
改动后
package softwaredesign;
public class Buy {
private Things things;
public Buy(Things things){
this.things = things;
}
public void buy(){
things.buyThins();
}
}
具体的测试
package softwaredesign;
public class Test {
public static void main(String[] args) {
Buy b = new Buy(new BuyMouse());
b.buy();
}
}
构造器使用方式并不是非常方便 需要拓展的时候 还有new一个出来Buy实现类
在Spring框架中默认的设计模式是 单例模式
之前是构造器方式
现在取消构造器方式 改用Setter方法
修改Buy类
package softwaredesign;
public class Buy {
private Things things;
public void setThings(Things things) {
this.things = things;
}
public void buy(){
things.buyThins();
}
}
测试
package softwaredesign;
public class Test {
public static void main(String[] args) {
Buy b = new Buy();
b.setThings(new BuyMouse());
b.buy();
}
}
看看现在的类图
Buy类不依赖与任何继承Things的类
单一职责原则
定义:不要存在多于一个导致类变更的原因
一个类/接口/方法只负责一项职责
优点:降低类的复杂度、提高类的可读性,提高系统的可维护性、降低变更引起的风险
下面进行演示:
创建一个鸟类 违反单一原则
package softwareDesign;
public class Bird {
public void mainMoveMode(String birdName){
if ("变色龙".equals(birdName)){
System.out.println("变色龙用四肢走");
}else {
System.out.println(birdName+"用翅膀飞");
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
测试
package softwareDesign;
public class Test {
public static void main(String[] args) {
Bird bird = new Bird();
bird.mainMoveMode("猫头鹰");
bird.mainMoveMode("变色龙");
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
假如又要添加新的东西 又要去动Bird类了(因为Bird类负责的职责较多),随着代码的复杂,越动出错的风险越大
改变:
package softwareDesign;
public class FlyBird {
public void mainMoveMode(String birdName) {
System.out.println(birdName + "用翅膀飞");
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
package softwareDesign;
public class WalkAnimal {
public void mainMoveMode(String Name) {
System.out.println(Name + "用四肢走");
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
package softwareDesign;
public class Test {
public static void main(String[] args) {
FlyBird flyBird = new FlyBird();
WalkAnimal walkAnimal = new WalkAnimal();
//应用层判断
flyBird.mainMoveMode("猫头鹰");
walkAnimal.mainMoveMode("变色龙");
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
我们来看一下类图
当然还有接口 以及 方法的情况
结合上面的示例 这个都很好理解的
接口隔离原则
定义:用多个专门的接口,而不使用单一的总接口,客户端不应该依赖它不需要的接口
一个类对一个类的依赖应该建立在最小的接口上
建立单一接口,不要建立庞大臃肿的接口尽量细化接口,接口中的方法尽量少
尽量细化接口,接口中的方法尽量少
注意适度原则,一定要适度
优点:符合我们常说的高内聚低耦合的设计思想从而使得类具有很好的可读性、可扩展性和可维护性。
比如说 一个接口中定义了eat() fly() swim()
狗继承了这个接口 但是多了一个fly()
猫头鹰记录了这个接口 但是多一个swim()
它们都要空实现
如果要符合接口隔离原则,需要为fly(0 eat()
和 swim() eat() 或者 为fly() eat() swim()分别定义一个接口
但是一定要适度适度 差不多细化就行了 否则会导致接口过多 反而增大开发难度
迪米特法则(最少知道原则)
定义:一个对象应该对其他对象保持最少的了解。又叫最少知道原则
尽量降低类与类之间的耦合
优点:降低类之间的耦合、
这个也是需要适度的!!!
强调只和朋友交流,不和陌生人说话
朋友:
出现在成员变量、方法的输入、输出参数中的类称为成员朋友类,而出现在方法体内部的类不属于朋友类。
演示
不符合迪米特法则的情形:
package softwareDesign;
import java.util.ArrayList;
import java.util.List;
public class Boss {
public void commandCheckNumber(TeamLeader teamLeader){
List<Project> list = new ArrayList<>();
for (int i = 0; i <3 ; i++) {
list.add(new Project());
}
teamLeader.checkNumberOfProject(list);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
package softwareDesign;
import java.util.List;
public class TeamLeader {
public void checkNumberOfProject(List<Project> projectList){
System.out.println("项目数量是: "+projectList.size());
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
package softwareDesign;
public class Project {
}
- 1
- 2
- 3
- 4
- 5
测试
package softwareDesign;
public class Test {
public static void main(String[] args) {
Boss boss = new Boss();
TeamLeader teamLeader = new TeamLeader();
boss.commandCheckNumber(teamLeader);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
我们看看它的类图
从代码中可以分析出
Boss类中 可以跟Project类没有关系的 直接调用TeamLeader就行了
修改:
package softwareDesign;
public class Boss {
public void commandCheckNumber(TeamLeader teamLeader){
teamLeader.checkNumberOfProject();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
package softwareDesign;
import java.util.ArrayList;
import java.util.List;
public class TeamLeader {
public void checkNumberOfProject(){
List<Project> list = new ArrayList<>();
for (int i = 0; i <3 ; i++) {
list.add(new Project());
}
System.out.println("项目数量是: "+list.size());
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
package softwareDesign;
public class Project {
}
- 1
- 2
- 3
- 4
- 5
测试
package softwareDesign;
public class Test {
public static void main(String[] args) {
Boss boss = new Boss();
TeamLeader teamLeader = new TeamLeader();
boss.commandCheckNumber(teamLeader);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
只有 Boss和TeamLeader类 做了改动
下面是类图:
里氏替换原则
定义:
如果对每一个类型为T1的对象o1,都有类型为T2的对象02,使得以T1定义的所有程序P在所有的对象o1都替换成o2时,程序P的行为没有发生变化,那么类型T2是类型T1的子类型。
定义扩展:
一个软件实体如果适用一个父类的话,那一定适用于其子类,所有引用父类的地方必须能透明地使用其子类的对象,子类对象能够替换父类对象,而程序逻辑不变。
引申意义:子类可以扩展父类的功能,但不能改变父类原有的功能。
◆含义1:子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
◆含义2:子类中可以增加自己特有的方法。
结合"开闭原则"举的例子 可以很容易看出来 前两条的出入
◆含义3:当子类的方法重载父类的方法时,方法的前置条件(即方法的输入/入参)要比父类方法的输入参数更宽松。
◆含义4:当子类的方法实现父类的方法时(重写/重载或实现抽象方法)方法的后置条件(即方法的输出/返回值)要比父类更严格或相等。
它的优点:
◆优点1:约束继承泛滥,开闭原则的一种体现。
◆优点2:加强程序的健壮性,同时变更时也可以做到非常好的兼容性提高程序的维护性、扩展性。降低需求变更时引入的风险。
演示:
Rectangle.java
package softwareDesign;
public class Rectangle {
private long length;
private long width;
public long getLength() {
return length;
}
public long getWidth() {
return width;
}
public void setLength(long length) {
this.length = length;
}
public void setWidth(long width) {
this.width = width;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
Squre.java
package softwareDesign;
public class Square extends Rectangle{
private long sideLength;
public long getSideLength() {
return sideLength;
}
public void setSideLength(long sideLength) {
this.sideLength = sideLength;
}
@Override
public long getLength() {
return getSideLength();
}
@Override
public long getWidth() {
return getLength();
}
@Override
public void setLength(long length) {
setSideLength(length);
}
@Override
public void setWidth(long width) {
setSideLength(width);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
Test.java
package softwareDesign;
public class Test {
public static void resize(Rectangle rectangle) {
while (rectangle.getWidth()<=rectangle.getLength()){
rectangle.setWidth(rectangle.getWidth()+1);
System.out.println(""+rectangle.getWidth()+" length:"+rectangle.getLength());
}
System.out.println("方法结束:"+rectangle.getWidth()+" length:"+rectangle.getLength());
}
public static void main(String[] args){
Rectangle rectangle = new Rectangle();
rectangle.setWidth(10);
rectangle.setLength(20);
resize(rectangle);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
注意测试的是 长方形的实例
如果换成了正方形 那么这个程序将会永久的执行下去 直到溢出
不符合 里氏替换原则
我们可以改进一下:
我们可以创建一个基于长方形和正方形的父类:
package softwareDesign;
public interface QRangle {
long getWidth();
long getLength();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
package softwareDesign;
public class Rectangle implements QRangle{
private long length;
private long width;
public long getLength() {
return length;
}
public long getWidth() {
return width;
}
public void setLength(long length) {
this.length = length;
}
public void setWidth(long width) {
this.width = width;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
package softwareDesign;
public class Square implements QRangle{
private long sideLength;
public long getSideLength() {
return sideLength;
}
public void setSideLength(long sideLength) {
this.sideLength = sideLength;
}
public long getWidth() {
return sideLength;
}
public long getLength() {
return sideLength;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
这样子在测试中 使用之前的方式 会报错 因为他不满足里氏替换原则
合成/复用原则(组合/复用原则)
定义:尽量使用对象组合/聚合,而不是继承关系达到软件复用的目的
聚合has-A和组合contains-A
优点:可以使系统更加灵活,降低类与类之间的耦合度,一个类的变化对其他类造成的影响相对较少
不满足的设计
package softwareDesign;
public class DBConnection {
public String getConnection(){
return "MySQL数据库连接";
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
package softwareDesign;
public class ProductDao extends DBConnection {
public void addProduct(){
String conn = super.getConnection();
System.out.println("使用"+conn+"增加产品");
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
Test
package softwareDesign;
public class Test {
public static void main(String[] args){
ProductDao productDao = new ProductDao();
productDao.addProduct();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
UML类图
如果又接入了别的数据库
我们能够直接修改DBConnection类
但是这样违反开闭原则
我们重构一下它 让它在具备扩展能力且不违反开闭原则,还遵守合成/复用原则
抽象类
package softwareDesign;
public abstract class DBConnection {
public abstract String getConnection();
}
- 1
- 2
- 3
- 4
- 5
- 6
package softwareDesign;
public class MySQLConnection extends DBConnection{
@Override
public String getConnection() {
return "MySQL数据库连接";
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
package softwareDesign;
public class PostgreSQLConnection extends DBConnection {
@Override
public String getConnection() {
return " PostgreSQL数据库连接";
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
package softwareDesign;
public class ProductDao {
private DBConnection dbConnection;
public void setDbConnection(DBConnection dbConnection) {
this.dbConnection = dbConnection;
}
public void addProduct(){
//通过组合的方式
String conn =dbConnection.getConnection();
System.out.println("使用"+conn+"增加产品");
}
}
测试
package softwareDesign;
public class Test {
public static void main(String[] args){
ProductDao productDao = new ProductDao();
productDao.setDbConnection(new MySQLConnection());
productDao.addProduct();
}
}
- 查看UML图
凡是拓展的都是第二层的平级