第三周课程总结

发布于: 2020 年 06 月 24 日

设计模式

什么是设计模式?

  1. 每一种设计模式都描述了一种问题的通用解决方案。这种问题在我们的环境中,不停地的出现

  2. 设计模式是一种可重复使用的解决方案。

设计模式的组成

  1. 模式的名称 - 少量的字组成的名称

  2. 待解决问题 - 描述了何时需要运用这种模式,以及运用模式的环境

  3. 解决方案 - 描述了组成设计的元素(类和对象),它们的关系、职责以及合作。但这种解决方案是抽象的,它不代表具体实现

  4. 结论 - 运用这种方案所带来的利弊。主要是指它对系统的弹性、扩展性、可移植性

如何创建一个对象?(工厂模式)

需求(V1):程序需要根据文件的后缀名,选择不同的解析类进行文件解析。解析的内容是User信息。

实现:

public class User{
private int id;
private String name;
private String password;
// ...
// getters / setters
}
public interface UserConfigReader{
User parse(InputStream inputStream);
}
public class JsonUserConfigReader implements UserConfigReader{
public User read(InputStream inputStream){
// 解析用户信息
}
}
public class XmlUserConfigReader implements UserConfigReader{
public User read(InputStream inputStream){
// 解析用户信息
}
}
// ....
public class Application{
public static void main(String[] args){
// 仿佛有一坨坨业务代码
File file = new File(...);
InputStream inputStream = new FileInputStream(file);
// ------------- choose UserConfigReader -----------
UserConfigReader reader = null;
if(file.getFileName().endsWith(".json")){
reader = new JsonUserConfigReader();
}else if(file.getFileName().endsWith(".xml")){
reader = new XmlUserConfigReader();
}
if(reader == null){
throw NoSuchUserConfigReaderException();
}
User user = reader.read(inputStream);
// ------------- choose UserConfigReader -----------
System.out.println(user);
// 仿佛有一坨坨业务代码
}
}

如果增加了一个用户配置解析类(PropertiesUserConfigReader),那么就需要改变choose UserConfigReader块逻辑。

会产生如下问题:

  1. 定位代码(不知道choose UserConfigReader代码逻辑在哪,按照正常思路来讲,choose UserConfigReader 不应该掺杂在业务代码中)

  2. choose UserConfigReader 代码复用问题

存在的问题:UserConfigReader的创建逻辑的无扩展性不好,UserConfigReader创建代码无法复用。

解决方案 V1

public class UserConfigReaderFactory{
public UserConfigReader choose(String fileName){
if(fileName.endsWith(".json")){
return new JsonUserConfigReader();
}else if(fileName.endWith(".xml")){
return new XmlUserConfigReader();
}
}
}
public class Application{
private UserConfigReaderFactory userConfigReaderFactory = new UserConfigReaderFactory();
public static void main(String[] args){
// 仿佛有一坨坨业务代码
File file = new File(...);
InputStream inputStream = new FileInputStream(file);
UserConfigReader userConfigReaderFactory = userConfigReaderFactory.choose(file.getFileName());
System.out.println(user);
// 仿佛有一坨坨业务代码
}
}

以上代码解决了哪些问题?

  1. 代码复用问题(将choose逻辑统一封装在UserConfigReaderFactory中)

  2. 我们如果需要添加PropertiesUserConfigReader,可以直接定位到UserConfigReaderFactory类中,进行逻辑修改。但并不符合OCP原则。

存在的问题:以上无法满足OCP原则

解决方案 V2

public class UserConfigReaderFactory{
private static final Map<String,UserConfigReader> userConfigReaderMap = new HashMap<>();
static{
// 加入默认的一些UserConfigReader
}
public static void registryUserConfigReader(String suffix,UserConfigReader userConfigReader){
if(userConfigReaderMap.get(suffix) != null){
// 如果不允许重复 抛异常
}
userConfigReaderMap.put(suffix,userConfigReader);
}
public static UserConfigReader getUserConfigReader(String fileName){
// 取到fileName的后缀
UserConfigReader userConfigReader = userConfigReaderMap.get(suffix);
if(userConfigReader == null){
throw new UserConfigReaderNotFoundException();
}
return userConfigReader;
}
}
public class Application{
public static void main(String[] args){
// 仿佛有一坨坨业务代码
File file = new File(...);
InputStream inputStream = new FileInputStream(file);
UserConfigReader reader = UserConfigReaderFactory.getUserConfigReader(file.getFileName());
// ....
// 仿佛有一坨坨业务代码
}
}

以上代码解决了哪些问题:

  1. 基本满足OCP原则

解决方案V1 VS 解决方案V2

解决方案V1

优点:

  1. 调用工厂方法获取的对象,每次都是新创建的,支持不可重复使用的对象。

  2. if else分支少时,可读性较好。

缺点:

  1. 扩展时,不满足OCP原则。

  2. if else分支很多时,可读性较差。

  3. 调用方法获取对象,每次都是新创建对象,浪费内存,性能相对于解决方案V2较差。

  4. 如果工厂生成的对象在创建时,过程十分复杂,会导致工厂方法非常混乱。

解决方案V2

优点:

  1. 调用工厂方法获取的对象,每次都是获取Map中的缓存对象,避免对象重复创建的消耗,节约内 存。相对于解决方案V1性能较好。

  2. 基本满足OCP原则(如果需要注册其他UserConfigReader,在运行期直接调用registryUserConfigReader方法注册对应的UserConfigReader即可)。

缺点:

  1. 不支持不可重复使用的对象。

  2. 如果工厂生成的对象在创建时,过程十分复杂,会导致工厂方法非常混乱。

解决方案V2.1(配置文件)

public interface UserConfigReaderRegistry{
void registry(String suffix,UserConfigReader reader);
}
public UserConfigReaderFactory implements UserConfigReaderRegistry{
private final Map<String,UserConfigReader> userConfigReaderMap = new HashMap<>();
@Override
public void registry(String suffix,UserConfigReader reader){
// userConfigReaderMap.put(suffix,reader);
}
public UserConfigReader getUserConfigReader(String suffix){
// 从Map中获取
}
}
// 该读取类还可以继续抽象,UserConfigReader的配置方式可以是Properties、Xml
public class UserConfigReaderSourceReader {
public void load(UserConfigReaderRegistry reg){
// 读取配置文件 将配置文件的UserConfigReader 加入到对应的UserConfigReaderRegistry中
reg.registry(suffix,reader);
}
}
public class Application{
public static void main(String[] args){
UserConfigReaderFactory factory = new UserConfigReaderFactory();
UserConfigReaderSourceReader sourceReader = new UserConfigReaderSourceReader();
sourceReader.load(factory);
}
}

优点 相比于 解决方案V2:

  1. 配置文件更加灵活

缺点

  1. 增加功能复杂度

存在的问题:如果工厂生成的对象在创建时,过程十分复杂,会导致工厂方法非常混乱。

例如:

创建JsonUserConfigReader、XmlUserConfigReader实例时,需要较复杂的初始化方法,使用以上方法就并不理想了。

需要使用工厂方法模式来解决该问题。

public interface UserConfigReaderFactory{
UserConfigReader getUserConfigReader(String suffix);
}
public class JsonUserConfigReaderFactory implements UserConfigReaderFactory{
public UserConfigReader getUserConfigReader(String suffix){
JsonUserConfigReader reader = new JsonUserConfigReader();
reader.init();// 一些比较复杂的操作
}
}
public class XmlUserConfigReaderFactory implements UserConfigReaderFactory{
public UserConfigReader getUserConfigReader(String suffix){
XmlUserConfigReader reader = new XmlUserConfigReader();
// ... 复杂的初始化操作
}
}

工厂方法模式隔离了复杂了初始化操作,如果这些初始化都挤在一个方法里可读性会很差。

如何使用工厂方法模式呢?直接new吗?

一般来讲使用简单工厂模式生成工厂方法模式的工厂。

工厂模式总结

解决方案V1很明显违反了OCP原则,那它就不会被使用吗?

考虑到当前所面对的场景还是很重要的!如果当前类逻辑并不是特别复杂,if分支并不是很多,可以接受这种方式。

很多优秀的开源框架也在使用这种方式

来源于Netty4

public final class DefaultEventExecutorChooserFactory{
public EventExecutorChooser newChooser(EventExecutor[] executors) {
if (isPowerOfTwo(executors.length)) {
return new PowerOfTwoEventExecutorChooser(executors);
} else {
return new GenericEventExecutorChooser(executors);
}
}
}

来源于JDK

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;
}

工厂模式的本质:隔离复杂的对象创建逻辑,将对象创建逻辑委派给对象工厂来进行创建。

对象创建一次就够了!(单例设计模式)

单例设计模式本质:避免浪费内存,减轻GC压力,一个对象只创建一次。

单例模式 饿汉式

public class SingletonObject{
public static final Object object = new Object();
public static Object getObject(){
return object;
}
}

单例模式 懒汉式

public class SingletonObject{
public static Object object ;
public static synchronized Object getObject(){
if(object == null){
object = new Object();
}
return object;
}
}

单例模式懒汉式 加强版 DCL

public class SingletonObject{
public static volatile Object object;
public static Object getObject(){
if(object == null){
synchronized(SingletonObject.class){
if(object == null){
object = new Object();
}
}
}
return object;
}
}

饿汉式

优点:

  1. 简单易懂

缺点:

  1. 在这个类还不需要时,就已经被创建了,也就是说在这个类被加载到虚拟机的时候这个单例就被创建了

懒汉式

优点:

  1. 按需创建

  2. 相比于加强版饿汉式 简单易懂

缺点:

  1. 多线程访问每次都需要获取锁,效率低下,加强版饿汉式只需要在第一次访问创建对象时会竞争锁。

商品一会儿一个价格怎么办?(策略模式)

需求:商品在上午9-11点时打9折,11点至18点时打8折,其余时间不打折。

public class Order{
public void calcPrice(double price){
// if 当前时间 9 - 11 点 返回打9折价格
// else if 当前时间 11 - 18 点 返回打8折价格
// else 不打折
}
}

如果打折策略再增加,比如说周末,以及端午等打折策略。

这是一个比较易变的需求,营销也会经常组织打折活动。

public interface PriceStrategy{
double getPrice(double price);
}
public class NineHourPriceStrategy implements PriceStrategy{
}
// ....
public class Application{
public static void main(String[] args){
PriceStrategy ps = new XXX();
// 一般策略模式会结合着简单工厂模式一起使用
}
}

策略模式本质:抽象出一个接口用于描述算法,这个算法会有多种实现。

组合优于继承!(装饰器模式)

InputStream是对一个输入流的抽象

需求:

对InputStream进行增强,需要提供InputStream read缓冲区,InputStream read只可以读取byte,还需要提供可以readInt,readLong功能。

反例:

public abstract class InputStream{
public int read(byte[] bytes);
}
// 提供缓冲功能
public class BufferedInputStream extends InputStream{
public int read(byte[] bytes){
super.read() // ..
}
}
// 提供读取int long等功能
public class DataInputStream extends InputStream{
public int readInt(){
super.read()
// ...
}
}

现在又提出需求,既要支持readInt又要提供缓冲区,我们可能还会来一个BufferedDataInputStream,这只是两个功能,就需要创建三个类。基础功能的类,与排列组合来的类,这样类会爆炸。

装饰模式优化

// 继承InputStream 保证功能完整性
public class BufferedInputStream extends InputStream{
private InputStream inputStream;
public BufferedInputStream(InputStream inputStream){
this.inputStream = inputStream;
}
public int read(byte[] bytes){
// 缓冲
inputStream.read();
// 缓冲
}
}
public class DataInputStream extends InputStream{
private InputStream inputStream;
public BufferedInputStream(InputStream inputStream){
this.inputStream = inputStream;
}
public int readInt(){
// 缓冲
inputStream.read();
// 缓冲
}
}

这样如果它们需要排列组合

new DataInputStream(new BufferedInputStream())

优点:

  1. 相比于继承 不会像继承那样因功能排列组合导致更多的类。

装饰器模式本质:在原有类的基础上,增强原有功能。

与代理模式、AOP的区别,代理模式、AOP适合做与本身类功能无关的功能,例如记日志、拦截器权限控制等。

用户头像

Geek_a327d3

关注

还未添加个人签名 2020.04.14 加入

还未添加个人简介

评论

发布
暂无评论
第三周课程总结