Java23种设计模式系列——设计原则day1-2
软件设计原则
在软件开发中,为了提高软件系统的可维护性和可复用性,增加软件的可扩展性和灵活性,程序员要尽量根据6条原则来开发程序,从而提高软件开发效率、节约软件开发成本和维护成本。
开闭原则
对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。
想要达到这样的效果,我们需要使用接口和抽象类。
因为抽象灵活性好,适应性广,只要抽象的合理,可以基本保持软件架构的稳定。而软件中易变的细节可以从抽象派生来的实现类来进行扩展,当软件需要发生变化时,只需要根据需求重新派生一个实现类来扩展就可以了。
开闭原则示例
架构即思路
整体来说使用Skill作为主接口,由AbstractUserSkill细化以及构造公用的方法,实际在UserSkill中进行实现
代码
Skill接口
专注于提供抽象的方法即可
package open_and_close;
public interface Skill {
void showSkills();
void setUserSkills();
}
AbstractUserSkill
实现Skill接口,写公用方法showSkills
package open_and_close;
import java.util.ArrayList;
public abstract class AbstractUserSkill implements Skill{
public static final int SPECIAL_ID = 10000;
protected ArrayList<String> userSkills;
@Override
public void showSkills() {
this.userSkills.forEach(System.out::println);
}
@Override
public abstract void setUserSkills();
}
UserSkill1
package open_and_close;
import java.util.ArrayList;
public class UserSkill1 extends AbstractUserSkill {
public static final int SPECIAL_ID = 10001;
private ArrayList<String> userSkills = new ArrayList<>();
public UserSkill1() {
this.setUserSkills();
}
@Override
public void setUserSkills() {
this.userSkills.add("UserSkill1:skill1");
this.userSkills.add("UserSkill1:skill2");
super.userSkills = this.userSkills;
}
}
UserSkill2
package open_and_close;
import java.util.ArrayList;
public class UserSkill2 extends AbstractUserSkill {
public static final int SPECIAL_ID = 10002;
private ArrayList<String> userSkills = new ArrayList<>();
public UserSkill2() {
this.setUserSkills();
}
@Override
public void setUserSkills() {
this.userSkills.add("UserSkill2:skill1");
this.userSkills.add("UserSkill2:skill2");
super.userSkills = this.userSkills;
}
}
测试
测试入口类
Game类是对Skill的应用实现
package open_and_close.apps;
import open_and_close.Skill;
public class Game1 {
private Skill skill;
public Game1(Skill skill) {
this.skill = skill;
}
public Game1() {
}
public void setSkill(Skill skill) {
this.skill = skill;
}
public void changeUserSkill() {
this.skill.showSkills();
}
}
测试类
真正的业务代码
package open_and_close;
import open_and_close.apps.Game1;
import java.util.Scanner;
public class Test {
public static void main(String[] args) {
Game1 game = new Game1();
System.out.println("选择角色(输入:1,2)");
Scanner scanner = new Scanner(System.in);
int chooseID = scanner.nextInt();
if (chooseID == UserSkill1.SPECIAL_ID - AbstractUserSkill.SPECIAL_ID) {
game.setSkill(new UserSkill1());
}
if (chooseID == UserSkill2.SPECIAL_ID - AbstractUserSkill.SPECIAL_ID) {
game.setSkill(new UserSkill2());
}
game.changeUserSkill();
}
}
结果
里氏代换原则
里氏代换原则是面向对象设计的基本原则之一。
里氏代换原则:任何基类可以出现的地方,子类一定可以出现。
通俗理解︰子类可以扩展父类的功能,但不能改变父类原有的功能。换句话说,子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。
如果通过重写父类的方法来完成新的功能,这样写起来虽然简单,但是整个继承体系的可复用性会比较差,特别是运用多态比较频繁时,程序运行出错的概率会非常大。
依赖倒转原则
高层模块不应该依赖低层模块,两者都应该依赖其抽象,抽象不应该依赖细节,细节应该依赖抽象。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。
即在一个类(高层)中,有一个属性是类(低层)的抽象类或接口,而不是一个实习类
依赖倒转原则示例
设计一个手机的充电器接口
架构即思路
代码
Charger接口
package depend_reverse;
public interface Charger {
void runCharger();
}
MiCharger实现类
package depend_reverse;
public class MiCharger implements Charger{
private String name;
private int power;
private double price;
public MiCharger() {
this.name = "小米充电器";
this.power = 220;
this.price = 39.9;
}
@Override
public void runCharger() {
System.out.println(name);
System.out.println(power);
System.out.println(price);
}
}
测试类(Phone)
package depend_reverse;
public class Phone {
private Charger charger;
public Charger getCharger() {
return charger;
}
public void setCharger(Charger charger) {
this.charger = charger;
}
public void getElectric(){
charger.runCharger();
}
public static void main(String[] args) {
Phone phone = new Phone();
phone.setCharger(new MiCharger());
phone.getElectric();
}
}
结果
接口隔离原则
客户端不应该被迫依赖于它不使用的方法,一个类对另一个类的依赖应该建立在最小的接口上。
即我们应该将各类抽取为一个个接口,使用到才实现,不使用实现
接口隔离原则示例
架构即思路
两个接口各防止需要实现的方法,老年机只要实现Call,SendMsg不需要也就不用实现接口
代码
SendMsg
package interface_insulate;
public interface SendMsg {
void sendMsg();
}
Call
package interface_insulate;
public interface Call {
void Call();
}
OldPhone
package interface_insulate;
public class OldPhone implements Call{
private String name;
private String function;
@Override
public void Call() {
this.function = "calling ...";
this.name = "老年机";
System.out.println(name+":"+function);
}
}
测试
package interface_insulate;
public class Test {
public static void main(String[] args) {
OldPhone oldPhone = new OldPhone();
oldPhone.Call();
}
}
结果
迪米特法则
又叫最少知识原则
只和你的直接朋友交谈,不跟"陌生人"说话
其含义是:如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。
迪米特法则中的"朋友"是指:当前对象本身、当前对象的成员对象、当前对象所创建的对象、当前对象的方法参数等,这些对象同当前对象存在关联、聚合或组合关系,可以直接访问这些对象的方法
迪米特示例
架构即思路
商品买卖时只有中间商知道卖家和买家,买家和卖家互不认识
代码
Seller
package demite;
public class Seller {
private String name;
private int money;
private String goods;
public Seller(String name) {
this.name = name;
this.goods = "二手书";
this.money = 0;
}
public String getName() {
return name;
}
public String getGoods() {
return goods;
}
public void sell(String goods, int price, Agent agent){
agent.showGoods(goods,price);
}
public void getMoney(String goods,int price) {
this.goods = "";
this.money += price;
System.out.println("卖了:"+goods+"赚了:"+price+"还剩下:"+this.goods);
}
}
Buyer
package demite;
public class Buyer {
private String name;
private int money;
private String goods;
public Buyer(String name) {
this.name = name;
this.money = 500;
this.goods = null;
}
public void buy(String goods,int cost,Agent agent){
agent.send(goods,cost);
}
public void costMoney(String goods,int cost){
this.goods = goods;
this.money -= cost;
System.out.println("买了:"+goods+"花了:"+cost+"还剩下:"+this.money);
}
}
Agent
package demite;
import java.util.HashMap;
import java.util.Objects;
public class Agent {
private Buyer buyer;
private Seller seller;
private HashMap<String, Object> temp = new HashMap<>();
public Agent(Buyer buyer, Seller seller) {
this.buyer = buyer;
this.seller = seller;
}
public Agent() {
}
public Buyer getBuyer() {
return buyer;
}
public void setBuyer(Buyer buyer) {
this.buyer = buyer;
}
public Seller getSeller() {
return seller;
}
public void setSeller(Seller seller) {
this.seller = seller;
}
public void send(String goods, int cost) {
this.temp.remove("goods", goods);
this.temp.remove("price", cost);
if (this.temp.size() == 0) {
this.buyer.costMoney(goods, cost);
this.seller.getMoney(goods, cost);
}
System.out.println("交易完成");
}
public void showGoods(String goods, int price) {
System.out.println(this.seller.getName() + "卖:" + goods + "价格为:" + price);
this.temp.put("goods", goods);
this.temp.put("price", price);
}
}
测试
package demite;
public class Test {
public static void main(String[] args) {
Agent agent = new Agent();
Seller seller = new Seller("张三");
Buyer buyer = new Buyer("小王");
agent.setBuyer(buyer);
agent.setSeller(seller);
seller.sell(seller.getGoods(),30,agent);
buyer.buy(seller.getGoods(),30,agent);
}
}
结果
合成复用原则
合成复用原则是指:尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。
通常类的复用分为继承复用和合成复用两种。
继承复用虽然有简单和易实现的优点,但它也存在以下缺点:
- 继承复用破坏了类的封装性。因为继承会将父类的实现细节暴露给子类,父类对子类是透明的,所以这种复用又称为白箱"复用。
- 子类与父类的耦合度高。父类的实现的任何改变都会导致子类的实现发生变化,这不利于类的扩展与维护。
- 它限制了复用的灵活性。从父类继承而来的实现是静态的,在编译时已经定义,所以在运行时不可能发生变化。
采用组合或聚合复用时,可以将已有对象纳入新对象中,使之成为新对象的一部分
新对象可以调用已有对象的功能,它有以下优点:
- 它维持了类的封装性。因为成分对象的内部细节是新对象看不见的,所以这种复用又称为"黑箱"复用。
- 对象间的耦合度低。可以在类的成员位置声明抽象。
- 复用的灵活性高。这种复用可以在运行时动态进行,新对象可以动态地引用与成分对象类型相同的对象。
总结
- 开闭原则:扩展易维护,接口为规范,实现为使用,实现热插拔
- 里氏代换:不要质疑前人的智慧,我们要站在巨人的肩膀上(子类不重写,只扩展功能)
- 依赖倒转:对抽象进行编程,使用抽象,隐藏具体实现,降低耦合
- 接口隔离:用多少,拿多少,对接口方法实现必要的,不出现任何冗余
- 迪米特:中介助力降低风险提升效率
- 合成复用:高内聚低耦合(多聚合,少继承),易扩展易复用