设计模式总览
用好设计模式具有优化代码的作用,就拿一个生活中的例子来说,普通人聚会抒发感受,就会说”喝酒唱歌,人生真爽“,而诗人抒发感受就会说:”对酒当歌,人生几何“。同样的一件事情,用不同的方式去表达,就会有不一样的意境。
举个简单的代码例子,比如说现在有以下几种业务场景
真实场景一:(直播)腾讯云直播Api文档中,只支持填写一个接口回调地址,需实现多个回调场景逻辑
真实场景二:(社交)Feed流产品,Title会有多个频道,类似关注、推荐、汽车、热门等
真实场景三:(支付)APP支持微信、支付宝、招商银行等支付方式
以上三个案例很容易看出来,采用if-else一招解决,快准狠,但是存在一些弊端,每新增一种支付方式、新的频道、新的事件都需要修改核心逻辑,添加else if去实现功能,接下来讲讲我日常开发中最常用的几种解决方案,从简到难,大家自行选择。
传统方案一(if + else):
package designpattern.Introduction;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class ContentCenter {
public List<Map<String, String>> focusList(String type) {
// 伪代码
Map<String, String> map = new HashMap<>();
map.put("focus", "关注V1");
List<Map<String, String>> list = new ArrayList<>();
list.add(map);
return list;
}
public List<Map<String, String>> recommendList(String type) {
// 伪代码
return new ArrayList<>();
}
public List<Map<String, String>> carList(String type) {
// 伪代码
return new ArrayList<>();
}
public List<Map<String, String>> randomList(String type) {
// 伪代码
return new ArrayList<>();
}
}
package designpattern.Introduction;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.StringUtils;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Slf4j
@Service
@RequiredArgsConstructor
public class ContentListServiceV1 {
private final ContentCenter contentCenter;
public List<Map<String, String>> getContentList(String type) {
List<Map<String, String>> responseList = new ArrayList<>(16);
if (StringUtils.equals("focus", type)) {
responseList = contentCenter.focusList(type);
} else if (StringUtils.equals("recommend", type)) {
responseList = contentCenter.recommendList(type);
} else if (StringUtils.equals("car", type)) {
responseList = contentCenter.carList(type);
} else {
responseList = contentCenter.randomList(type);
}
return responseList;
}
}
可以发现,每来一个业务,就要新写一个if-else,这样的代码的耦合性过高,动应用层的代码可能会导致未知的其它业务调用报错。我们用工厂方法对其进行改造。
先定义一个枚举
package designpattern.Introduction.factoryrenovat;
import lombok.Getter;
import org.apache.commons.codec.binary.StringUtils;
import java.util.Arrays;
import java.util.Optional;
@Getter
public enum ContentListEnum {
/**
* 定义枚举
*/
FOCUS("focus", "关注列表"),
RECOMMEND("recommend", "推荐列表"),
CAR("car", "车主列表"),
DEFAULT("default", "默认");
private final String code;
private final String name;
ContentListEnum(String code, String name) {
this.code = code;
this.name = name;
}
public static ContentListEnum getEnumByCode(String code) {
Optional<ContentListEnum> first = Arrays.stream(ContentListEnum.values()).filter(p -> StringUtils.equals(p.getCode(), code)).findFirst();
return first.orElse(ContentListEnum.DEFAULT);
}
}
定义工厂加枚举获取对象
package designpattern.Introduction.factoryrenovat;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Slf4j
@Component
public class ContentFactoryV2 {
private static final Map<ContentListEnum, ContentListV2> MAP = new ConcurrentHashMap<>();
/**
* 默认事件类
*/
private static final ContentListV2 DEFAULT= new FocusListV2ServiceImpl();
// 版本升级或新增模块、插件(添加枚举,定好具体实现类)
static {
MAP.put(ContentListEnum.FOCUS, new FocusListV2ServiceImpl());
MAP.put(ContentListEnum.DEFAULT, DEFAULT);
}
public static List<Map<String, String>> contentListEvent(String type) {
ContentListV2 contentList = MAP.get(ContentListEnum.getEnumByCode(type));
return contentList == null ? DEFAULT.getContentList(type) : contentList.getContentList(type);
}
}
提供对外接口
package designpattern.Introduction.factoryrenovat;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
@Slf4j
@Service
@RequiredArgsConstructor
public class ContentListServiceV2 {
public List<Map<String, String>> getContentList(String type) {
// 通过工厂,传入type实现
return ContentFactoryV2.contentListEvent(type);
}
}
可以发现,通过以上方式,我们以后扩展业务只要增加实现类和增加枚举扩展就可以了,这样使得业务层代码耦合度降低,提高了代码的可扩展性。因此,学会对代码的重构是非常重要的。
经典框架都在用设计模式解决问题
像spring这样的流行企业级框架,里面有很多组件就用到了设计模式的
设计模式名称 | 举例 |
---|---|
工厂模式 | beafactofy |
装饰者模式 | beanWrapper |
代理模式 | AopProxy |
委派模式 | DispatcherServlet |
模板模式 | jdbcTemplate |
工厂模式详解
工厂模式由来
我们都知道人类社会是一步步发展过来的,先是最早的原始社会,那个时候采用自给自足的生产方式。后面渐渐的到了农耕社会,出现了小作坊生产,开始有了分工。随着工业革命的到来,出现了工厂生产。
到了现代,出现了现代,出现了现代流水线生产。随着时代的发展,生产程序越来越复杂,分工越来越明细化。但是操作者却越来越简单。工厂模式也是这样的,有着一步步的演变过程,程序越来越复杂,但是使用出现的问题却越来越少。
简单工厂模式
简单工厂模式用于创建对象场景较少的场景,客户端只需要传入工厂需要的参数,实现逻辑不用关心。
以课程创建发布为例子,现在有java,python,php等课程,目前定义的有Icourse课程接口和JavaCourse以及Python课程。
public interface ICourse {
/**
* 录制视频
* @return
*/
void record();
}
public class JavaCourse implements ICourse {
public void record() {
System.out.println("录制Java课程");
}
}
public class PythonCourse implements ICourse {
public void record() {
System.out.println("录制Python课程");
}
}
普通人写代码创建课程对象是以下方式:
public class SimpleFactoryTest {
public static void main(String[] args) {
ICourse course = new JavaCourse();
course.record();
ICourse course2 = new PythonCourse();
course2.record();
}
}
可以发现这种方式很不利于代码扩展的,每次新增课程都要修改调用层代码,不符合开闭原则和依赖倒置原则,迪米特原则。我们可以定义一个接口用工厂来动态创建课程。
第一步优化,将创建对象的逻辑搬到工厂里面。
public class CourseFactory {
public ICourse create(String name) {
if ("java".equals(name)) {
return new JavaCourse();
} else if ("python".equals(name)) {
return new PythonCourse();
} else {
return null;
}
}
}
这样客户端创建课程对象时只要传入工厂的名称就可以了,但是后面每一次新增课程对象,还需要更改工厂的逻辑,还是违背了开闭原则,依赖倒置原则。我们可以用反射的方式优化一下。
public ICourse create(String className){
try {
if (!(null == className || "".equals(className))) {
return (ICourse) Class.forName(className).newInstance();
}
}catch (Exception e){
e.printStackTrace();
}
return null;
}
但是这样仍然要传传类名,于是继续优化。
public ICourse create(Class<? extends ICourse> clazz){
try {
if (null != clazz) {
return clazz.newInstance();
}
}catch (Exception e){
e.printStackTrace();
}
return null;
}
客户端创建实例代码:
public class SimpleFactoryTest {
public static void main(String[] args) {
ICourse iCourse = CourseFactory.create(JavaCourse.class);
iCourse.record();
}
}
这样子我们创建对象就只要传类的class对象,唯一的缺陷就是目前创建对象的范围还是小范围的,对于大范围产品的扩展并不适用。
jdk中也有很多类用到了简单工厂,比如说Calendar的getInstanse
public static Calendar getInstance()
{
return createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT));
}
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;
}
工厂方法
用工厂方法创建对象将创建对象的细节抽象到子工厂,创建对象的细节有子工厂决定,在简单工厂模式中,一切产品都由一个工厂创建,当产品对象的细节逻辑有区分时,则不好控制,不符合单一原则的定义。用工厂方法,每一类产品对象由一个子工厂创建,符合单一职责原则。
还是以课程发布为例,定义javaFactory和pythonFactofy两个工厂分别来创建java课程和python课程。
先定义一个工厂模型:
public interface ICourseFactory {
ICourse create();
}
定义两个子工厂实现工厂模型
public class JavaCourseFactory implements ICourseFactory {
public ICourse create() {
return new JavaCourse();
}
}
public class PythonCourseFactory implements ICourseFactory {
public ICourse create() {
return new PythonCourse();
}
}
客户端创建实例:
public class FactoryMethodTest {
public static void main(String[] args) {
ICourseFactory factory = new PythonCourseFactory();
ICourse course = factory.create();
course.record();
factory = new JavaCourseFactory();
course = factory.create();
course.record();
}
}
工厂方法的应用场景
1.创建对象的逻辑由工厂子类实现;
2.子类工厂处理单一品类的产品;
3.创建对象需要大量重复代码;
4.一个类不需要关注产品的细节;
缺点:
创建的工厂子类过多,增加了系统的复杂度
增加了系统的抽象性。
抽象工厂
抽象工厂只对于一系列的产品类或者产品等级,都采用一个接口的方式定义,这样客户端在创建具体对象的时候,不需要关注具体对象的实现,只需要由对应的工厂方法去创建就可以了。由子工厂决定创建产品的类型。在由创建子产品的类型决定创建产品等级的类型。
比如说现在的课程有java课程和python课程,每们课程都有笔记和视频两个等级。这种情况下我们就可以用到抽象工厂。
先定义一个抽象工厂,抽象工厂需要定义创建产品等级的方法
public abstract class CourseFactory {
public void init(){
System.out.println("初始化基础数据");
}
protected abstract INote createNote();
protected abstract IVideo createVideo();
}
定义抽象工厂子类重写创建产品等级的方法:
public class JavaCourseFactory extends CourseFactory {
public INote createNote() {
super.init();
return new JavaNote();
}
public IVideo createVideo() {
super.init();
return new JavaVideo();
}
}
public class PythonCourseFactory extends CourseFactory {
public INote createNote() {
super.init();
return new PythonNote();
}
public IVideo createVideo() {
super.init();
return new PythonVideo();
}
}
创建java课程接口以及实现
public interface ICourse {
/**
* 录制视频
* @return
*/
void record();
}
public class JavaCourse implements ICourse {
public void record() {
System.out.println("录制Java课程");
}
}
public class PythonCourse implements ICourse {
public void record() {
System.out.println("录制Python课程");
}
}
创建笔记,视频产品等级接口和对应的实现。
public interface INote {
void edit();
}
public class JavaNote implements INote {
public void edit() {
System.out.println("编写Java笔记");
}
}
public class PythonNote implements INote {
public void edit() {
System.out.println("编写Python笔记");
}
}
public interface IVideo {
void record();
}
public class JavaVideo implements IVideo {
public void record() {
System.out.println("录制Java视频");
}
}
public class PythonVideo implements IVideo {
public void record() {
System.out.println("录制Python视频");
}
}
客户端测试调用:
public class AbstractFactoryTest {
public static void main(String[] args) {
JavaCourseFactory factory = new JavaCourseFactory();
factory.createNote().edit();
factory.createVideo().record();
}
}
通过抽象工厂,我们在创建产品等级的时候不需要关注细节,产品等级只要由产品决定,产品由抽象工厂实现类决定。这样的层级分明,每个类的职责单一。缺点是无论是产品的扩展和产品等级的扩展都需要增加实现或者接口以及工厂实现类。
利用工厂模式重构jdbc数据库连接
初始化
public abstract class Pool {
public String propertiesName = "connection-INF.properties";
private static Pool instance = null; // 定义唯一实例
/**
* 最大连接数
*/
protected int maxConnect = 100; // 最大连接数
/**
* 保持连接数
*/
protected int normalConnect = 10; // 保持连接数
/**
* 驱动字符串
*/
protected String driverName = null; // 驱动字符串
/**
* 驱动类
*/
protected Driver driver = null; // 驱动变量
/**
* 私有构造函数,不允许外界访问
*/
protected Pool() {
try
{
init();
loadDrivers(driverName);
}catch(Exception e)
{
e.printStackTrace();
}
}
/**
* 初始化所有从配置文件中读取的成员变量成员变量
*
*/
private void init() throws IOException {
InputStream is = Pool.class.getResourceAsStream(propertiesName);
Properties p = new Properties();
p.load(is);
this.driverName = p.getProperty("driverName");
this.maxConnect = Integer.parseInt(p.getProperty("maxConnect"));
this.normalConnect = Integer.parseInt(p.getProperty("normalConnect"));
}
/**
* 装载和注册所有JDBC驱动程序
*
* @param dri 接受驱动字符串
*/
protected void loadDrivers(String dri) {
String driverClassName = dri;
try {
driver = (Driver) Class.forName(driverClassName).newInstance();
DriverManager.registerDriver(driver);
System.out.println("成功注册JDBC驱动程序" + driverClassName);
} catch (Exception e) {
System.out.println("无法注册JDBC驱动程序:" + driverClassName + ",错误:" + e);
}
}
单例获取连接池对象
/**
* 创建连接池
*/
public abstract void createPool();
/**
*
* (单例模式)返回数据库连接池 Pool 的实例
*
* @param driverName 数据库驱动字符串
* @return
* @throws IOException
* @throws ClassNotFoundException
* @throws IllegalAccessException
* @throws InstantiationException
*/
public static synchronized Pool getInstance() throws IOException,
InstantiationException, IllegalAccessException,
ClassNotFoundException {
if (instance == null) {
instance.init();
instance = (Pool) Class.forName("org.jdbc.sqlhelper.Pool")
.newInstance();
}
return instance;
}
提供工厂获取连接池对象
package sqlhelper.org.jdbc.sqlhelper;
import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.*;
import java.util.Date;
/**
* 数据库链接池管理类
*
* @author Tom
*
*/
public final class DBConnectionPool extends Pool {
private int checkedOut; //正在使用的连接数
/**
* 存放产生的连接对象容器
*/
private Vector<Connection> freeConnections = new Vector<Connection>(); //存放产生的连接对象容器
private String passWord = null; // 密码
private String url = null; // 连接字符串
private String userName = null; // 用户名
private static int num = 0;// 空闲连接数
private static int numActive = 0;// 当前可用的连接数
private static DBConnectionPool pool = null;// 连接池实例变量
/**
* 产生数据连接池
* @return
*/
public static synchronized DBConnectionPool getInstance()
{
if(pool == null)
{
pool = new DBConnectionPool();
}
return pool;
}
/**
* 获得一个 数据库连接池的实例
*/
private DBConnectionPool() {
try
{
init();
for (int i = 0; i < normalConnect; i++) { // 初始normalConn个连接
Connection c = newConnection();
if (c != null) {
freeConnections.addElement(c); //往容器中添加一个连接对象
num++; //记录总连接数
}
}
}catch(Exception e)
{
e.printStackTrace();
}
}
/**
* 初始化
* @throws IOException
*
*/
private void init() throws IOException
{
InputStream is = DBConnectionPool.class.getResourceAsStream(propertiesName);
Properties p = new Properties();
p.load(is);
this.userName = p.getProperty("userName");
this.passWord = p.getProperty("passWord");
this.driverName = p.getProperty("driverName");
this.url = p.getProperty("url");
this.driverName = p.getProperty("driverName");
this.maxConnect = Integer.parseInt(p.getProperty("maxConnect"));
this.normalConnect = Integer.parseInt(p.getProperty("normalConnect"));
}
/**
* 如果不再使用某个连接对象时,可调此方法将该对象释放到连接池
*
* @param con
*/
public synchronized void freeConnection(Connection con) {
freeConnections.addElement(con);
num++;
checkedOut--;
numActive--;
notifyAll(); //解锁
}
/**
* 创建一个新连接
*
* @return
*/
private Connection newConnection() {
Connection con = null;
try {
if (userName == null) { // 用户,密码都为空
con = DriverManager.getConnection(url);
} else {
con = DriverManager.getConnection(url, userName, passWord);
}
System.out.println("连接池创建一个新的连接");
} catch (SQLException e) {
System.out.println("无法创建这个URL的连接" + url);
return null;
}
return con;
}
/**
* 返回当前空闲连接数
*
* @return
*/
public int getnum() {
return num;
}
/**
* 返回当前连接数
*
* @return
*/
public int getnumActive() {
return numActive;
}
/**
* (单例模式)获取一个可用连接
*
* @return
*/
public synchronized Connection getConnection() {
Connection con = null;
if (freeConnections.size() > 0) { // 还有空闲的连接
num--;
con = (Connection) freeConnections.firstElement();
freeConnections.removeElementAt(0);
try {
if (con.isClosed()) {
System.out.println("从连接池删除一个无效连接");
con = getConnection();
}
} catch (SQLException e) {
System.out.println("从连接池删除一个无效连接");
con = getConnection();
}
} else if (maxConnect == 0 || checkedOut < maxConnect) { // 没有空闲连接且当前连接小于最大允许值,最大值为0则不限制
con = newConnection();
}
if (con != null) { // 当前连接数加1
checkedOut++;
}
numActive++;
return con;
}
/**
* 获取一个连接,并加上等待时间限制,时间为毫秒
*
* @param timeout 接受等待时间(以毫秒为单位)
* @return
*/
public synchronized Connection getConnection(long timeout) {
long startTime = new Date().getTime();
Connection con;
while ((con = getConnection()) == null) {
try {
wait(timeout); //线程等待
} catch (InterruptedException e) {
}
if ((new Date().getTime() - startTime) >= timeout) {
return null; // 如果超时,则返回
}
}
return con;
}
/**
* 关闭所有连接
*/
public synchronized void release() {
try {
//将当前连接赋值到 枚举中
Enumeration allConnections = freeConnections.elements();
//使用循环关闭所用连接
while (allConnections.hasMoreElements()) {
//如果此枚举对象至少还有一个可提供的元素,则返回此枚举的下一个元素
Connection con = (Connection) allConnections.nextElement();
try {
con.close();
num--;
} catch (SQLException e) {
System.out.println("无法关闭连接池中的连接");
}
}
freeConnections.removeAllElements();
numActive = 0;
} finally {
super.release();
}
}
/**
* 建立连接池
*/
public void createPool() {
pool = new DBConnectionPool();
if (pool != null) {
System.out.println("创建连接池成功");
} else {
System.out.println("创建连接池失败");
}
}
}