软件架构设计原则和工厂模式
软件架构设计原则
开闭原则
开闭原则(Open-Closed Principle, OCP)是指一个软件实体,如类、模块和函数应该对扩展开放,对修改关闭。
依赖倒置原则
依赖倒置原则(Dependence Inversion Principle, DIP)是指设计代码结构时,高层模块不应该依赖底层模块,二者都应该依赖其抽象。抽象不应该依赖细节;细节应该依赖抽象。通过依赖倒置,可以减少类与类之间的耦合性,提高系统稳定性,提高代码的可读性和可维护性,并能降低修改程序所造成的风险。
单一职责原则
单一职责(Simple Responsibility Principle, SRP)是指不要存在多于一个导致类变更的原因。假设我们有一个Class负责两个职责,一旦发生需求变更,修改其中一个职责的逻辑代码,有可能导致另一个发生故障。
接口隔离原则
接口隔离原则(Interface Segregation Principle,ISP)是指用多个专门的接口,而不使用单一的总接口,客户端不应该依赖它不需要的接口。这个原则指导我们在设计接口的时候应该注意以下几点
- 一个类对一类的依赖应该建立在最小的接口之上。
- 建立单一接口,不要建立庞大臃肿的接口
- 尽量细化接口,接口中的方法尽量少(不是越少越好,一定要适度)
接口隔离原则符合我们常说的高内聚低耦合的设计思想,从而使得类具有很好的可读性。
迪米特法则
迪米特原则(Law of Demeter,LOD)是指一个对象应该对其他对象保持最少的了解,又叫最少知道原则(Least Knowledge Principle,LKP),尽量降低类与类之间的耦合。迪米特原则主要强调只和朋友交流,不和陌生人说话。
里氏替换原则
里氏替换原则(Liskov Substitution Principle,LSP)是指如果对每一个类型为T1的对象o1,都有类型T2的对象o2,使得以T1定义的所有程序P在所有的对象o1都替换成o2时,程序P的行为没有发生变化,那么类型T2是类型T1的子类型。
可以理解为一个软件实体如果使用一个父类的话,那一定是适用于其子类,所有引用父类的地方必须能透明的使用其子类的对象,子类对象能够替换父类对象,而程序逻辑不变。根据这个理解,我们总结一下:子类可以扩展分类的功能,但不能改变父类的功能。
- 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
- 子类中可以增加自己特有的方法。
- 当子类的方法重载父类的方法时,方法的前置条件(即方法的入参)要比父类方法的输入参数更宽松。
- 当子类的方法实现父类的方法时(重写/重载或实现抽象方法),方法的后置条件(即方法的输出)要比父类更严格或相等。
合成复用原则
合成复用原则(Composite/Aggregate Reuse Principle,CARP)是指尽量使用对象组合(has-a)/聚合(contains-a),而不是继承关系达到软件复用的目的。可以使系统更加灵活,降低类与类之间的耦合度,一个类的变化对其他类造成的影响相对较少。
总结:学习设计原则,是学习设计模式的基础。在实际开发过程中,并不是一定要求所有代码都遵循设计原则,我们要考虑人力、世间、成本、质量,不是可以追求完美,要在适当的场景遵循设计原则,体现的是一种平衡取舍,帮助我们设计出更加优雅的代码结构。
工厂模式
简单工厂模式
这个没什么说的,直接上代码…
public interface BMW {
void run();
}
public class BMW320 implements BMW {
@Override
public void run() {
System.out.println("BMW320 running");
}
}`
public class BMW520 implements BMW {
@Override
public void run() {
System.out.println("BMW520 running");
}
}
public class BMWFactory {
public static BMW createBMW(String type) {
if("bmw320".equals(type)) {
return new BMW320();
} else if("bmw520".equals(type)) {
return new BMW520();
} else {
throw new RuntimeException("没有这个类型");
}
}
}
public static void main(String[] args) {
BMW bmw320 = BMWFactory.createBMW("bmw320");
bmw320.run();
BMW bmw520 = BMWFactory.createBMW("bmw520");
bmw520.run();
}
首先,我们创建了宝马系列的产品,然后由宝马工厂生产你需要的产品。获取的方式是,你先告诉宝马产商,你需要宝马的那个产品,然后工厂会创建一个你需要的产品。
这种设计模式的缺点是:不符合开闭原则,每当新增一个产品,我们都需要去修改现有的代码,不利于扩展。
优点:只需传入一个参数,就可以获取你需要的对象,不需要知道对象创建的细节。
我们可以通过修改入参,来改善这个问题。
1.把入参改成包名+类名,然后通过反射来创建对象(需要强转)
2.直接传入一个class 对象,通过反射创建对象
简单工厂模式在JDK源码中的应用,例如Calendar.getInstance(),进入getInstance()方法后我们可以看到内部实现用的其实也是简单工厂
private static Calendar createCalendar(TimeZone zone,
Locale aLocale)
{
CalendarProvider provider =
LocaleProviderAdapter.getAdapter(CalendarProvider.class, aLocale)
.getCalendarProvider();
if (provider != null) {
try {
return provider.getInstance(zone, aLocale);
} catch (IllegalArgumentException iae) {
// fall back to the default instantiation
}
}
Calendar cal = null;
if (aLocale.hasExtensions()) {
String caltype = aLocale.getUnicodeLocaleType("ca");
if (caltype != null) {
switch (caltype) {
case "buddhist":
cal = new BuddhistCalendar(zone, aLocale);
break;
case "japanese":
cal = new JapaneseImperialCalendar(zone, aLocale);
break;
case "gregory":
cal = new GregorianCalendar(zone, aLocale);
break;
}
}
}
if (cal == null) {
// If no known calendar type is explicitly specified,
// perform the traditional way to create a Calendar:
// create a BuddhistCalendar for th_TH locale,
// a JapaneseImperialCalendar for ja_JP_JP locale, or
// a GregorianCalendar for any other locales.
// NOTE: The language, country and variant strings are interned.
if (aLocale.getLanguage() == "th" && aLocale.getCountry() == "TH") {
cal = new BuddhistCalendar(zone, aLocale);
} else if (aLocale.getVariant() == "JP" && aLocale.getLanguage() == "ja"
&& aLocale.getCountry() == "JP") {
cal = new JapaneseImperialCalendar(zone, aLocale);
} else {
cal = new GregorianCalendar(zone, aLocale);
}
}
return cal;
}
工厂方法模式
工厂方法模式(Factory Method Pattern)是指定义一个创建对象的接口,但让实现这个接口的类来决定实例化那个类,工厂方法让类的实例化推迟到子类中进行。
把上面的代码修改下:
public interface IFactory {
BMW createBMW();
}
public class BMW320Factory implements IFactory {
@Override
public BMW createBMW() {
return new BMW320();
}
}
public class BMW520Factory implements IFactory {
@Override
public BMW createBMW() {
return new BMW520();
}
}
测试代码:
public class Test {
public static void main(String[] args) {
IFactory bmw320Factory = new BMW320Factory();
BMW bmw320 = bmw320Factory.createBMW();
bmw320.run();
IFactory bmw520Factory = new BMW320Factory();
BMW bmw520 = bmw520Factory.createBMW();
bmw520.run();
}
}
类图:
Logback中的工厂模式就是使用的这种,这里就不细说了。
工厂方法适用于以下场景:
- 创建对象需要大量的重复代码
- 客户端不依赖于产品类实例如何被创建、实现等细节
- 一个类通过其子类来指定创建哪个对象
优点:符合开闭原则,易于扩展
缺点:类的个数容易过多,增加复杂度。增加了系统的抽象性和理解难度
抽象工厂模式
抽象工厂模式(Abstract Factory Pattern)是指提供一个创建一系列相关或相互依赖对象的接口,无须指定他们具体的类。在讲解抽象工厂之前,我们需要了解两个概念,产品等级结构和产品族,看下面的图:
从上图中看出有正方形,圆形和菱形三种图形,相同颜色深浅的就代表同一个产品族,相同形状的代表同一个产品等级结构。同样可以从生活中来举例,比如,美的电器生产多种家用电器。那么上图中,颜色最深的正方形就代表美的洗衣机、颜色最深的圆形代表美的空调、颜色最深的菱形代表美的热水器,颜色最深的一排都属于美的品牌,都是美的电器这个产品族。再看最右侧的菱形,颜色最深的我们指定了代表美的热水器,那么第二排颜色稍微浅一点的菱形,代表海信的热水器。同理,同一产品结构下还有格力热水器,格力空调,格力洗衣机。
接下来我们看下代码实现:
/**
* 空调
*/
public interface AirConditioning {
void refrigeration();
}
/**
* 洗衣机
*/
public interface WashingMachine {
void wash();
}
/**
* 热水器
*/
public interface WaterHeater {
void boilWater();
}
public class HisenseAirConditioning implements AirConditioning {
@Override
public void refrigeration() {
System.out.println("海信空调制冷");
}
}
public class HisenseWashingMachine implements WashingMachine {
@Override
public void wash() {
System.out.println("海信洗衣机洗衣");
}
}
public class HisenseWaterHeater implements WaterHeater {
@Override
public void boilWater() {
System.out.println("海信热水器热水");
}
}
public class MideaAirConditioning implements AirConditioning {
@Override
public void refrigeration() {
System.out.println("美的空调制冷");
}
}
public class MideaWashingMachine implements WashingMachine {
@Override
public void wash() {
System.out.println("美的洗衣机洗衣服");
}
}
public class MideaWaterHeater implements WaterHeater {
@Override
public void boilWater() {
System.out.println("美的热水器热水");
}
}
/**
* 电器工厂
*/
public interface ElectricalApplianceFactory {
/**
* 创建热水器
* @return
*/
WaterHeater createWaterHeater();
/**
* 创建空调
* @return
*/
AirConditioning createAirConditioning();
/**
* 创建洗衣机
* @return
*/
WashingMachine createWashingMachine();
}
public class HisenseFactory implements ElectricalApplianceFactory {
@Override
public WaterHeater createWaterHeater() {
return new HisenseWaterHeater();
}
@Override
public AirConditioning createAirConditioning() {
return new HisenseAirConditioning();
}
@Override
public WashingMachine createWashingMachine() {
return new HisenseWashingMachine();
}
}
public class HisenseFactory implements ElectricalApplianceFactory {
@Override
public WaterHeater createWaterHeater() {
return new HisenseWaterHeater();
}
@Override
public AirConditioning createAirConditioning() {
return new HisenseAirConditioning();
}
@Override
public WashingMachine createWashingMachine() {
return new HisenseWashingMachine();
}
}
public class Test {
public static void main(String[] args) {
ElectricalApplianceFactory electricalApplianceFactory = new HisenseFactory();
AirConditioning airConditioning = electricalApplianceFactory.createAirConditioning();
airConditioning.refrigeration();
WashingMachine washingMachine = electricalApplianceFactory.createWashingMachine();
washingMachine.wash();
WaterHeater waterHeater = electricalApplianceFactory.createWaterHeater();
waterHeater.boilWater();
electricalApplianceFactory = new MideaFactory();
WaterHeater waterHeater1 = electricalApplianceFactory.createWaterHeater();
waterHeater1.boilWater();
WashingMachine washingMachine1 = electricalApplianceFactory.createWashingMachine();
washingMachine1.wash();
AirConditioning airConditioning1 = electricalApplianceFactory.createAirConditioning();
airConditioning1.refrigeration();
}
}
上面代码描述了三个产品族热水器、空调和洗衣机,也描述了两个产品等级美的和海信。抽象工厂可以清晰的描述这样的一层复杂的关系。但是如果后期还要加产品,比如说冰箱,那么我们从抽象工厂到具体的工厂全部都要调整,不符合开闭原则。所以它也是有缺点的:
- 扩展困难,需要修改抽象工厂的接口
- 增加了系统的抽象和理解难度。