您的位置:首页 > 编程语言 > Java开发

【菜鸟学院】精通Spring框架——03工厂模式

2020-04-19 21:54 471 查看

源码:https://github.com/2020GetGoodOffer/springStudy

Spring是一个将设计模式用的淋漓尽致的经典框架,从类名就能看出,下面是一些列举:

设计模式名称 举例
工厂模式 BeanFactory
装饰者模式 BeanWrapper
代理模式 AopProxy
委派模式 DispatcherServlet
策略模式 HandlerMapping
适配器模式 HandlerAdapter
模板模式 JdbcTemplate
观察者模式 ContextLoaderListener

首先我们来学习工厂模式

简单工厂模式

简单工厂模式是指由一个工厂对象决定创建哪一种产品类的实例,但它不属于23种设计模式。简单工厂模式适用于工厂类负责创建的对象较少的场景,且客户端只需要传入工厂类的参数,对于如何创建对象不需要关心。

我们来看代码,还是以课程为例,定义一个课程标准ICourse接口:

public interface ICourse {
//录制课程
void record();
}

创建一个Java课程的实现类:

public class JavaCourse implements ICourse {

public void record() {
System.out.println("录制Java课程");
}
}

在客户端调用代码:

public static void main(String[] args) {
ICourse course=new JavaCourse();
course.record();
}

在上面的代码中,ICourse指向子类JavaCourse的引用,应用层代码需要依赖JavaCourse,如果业务继续扩展,那么客户端的以来会变得越来越臃肿。因此我们要想办法把这种依赖减弱,把创建细节隐藏起来。
使用简单工厂模式对代码进行优化,先增加课程类:

public class MySQLCourse implements ICourse {
public void record() {
System.out.println("录制MySQL课程");
}
}

创建工厂类CourseFactory:

public class CourseFactory {

public ICourse create(String name){
if("java".equals(name))
return new JavaCourse();
else if ("mysql".equals(name))
return new MySQLCourse();
else
return null;
}
}

修改客户端调用代码如下:

public static void main(String[] args) {
CourseFactory factory = new CourseFactory();
ICourse course = factory.create("java");
course.record();
}

客户端调用变简单了,但如果我们的业务继续扩展,要增加其他课程,那么工厂中的create方法就要每次根据产品的增加修改代码逻辑,不符合开闭原则。因此我们可以根据反射技术继续优化简单工厂模式:

public class CourseFactory {

public ICourse create(String className){
try{
if(!(className==null||className.equals(""))){
return (ICourse) Class.forName(className).newInstance();
}
}catch (Exception e){
e.printStackTrace();
}
return null;
}
}

修改客户端调用代码:
这里方便明确学习内容使用了中文包命名,实际开发请使用英文

public static void main(String[] args) {
CourseFactory factory = new CourseFactory();
ICourse course = factory.create("工厂模式.简单工厂模式.JavaCourse");
course.record();
}

优化之后,产品不断丰富的过程中不需要修改CourseFactory中的代码,但还有个问题是方法参数是字符串,可控性有待提升而且需要强制类型转换,再修改一下代码:

public class CourseFactory {

public ICourse create(Class<? extends ICourse> clazz){
try{
if(clazz!=null){
return clazz.getConstructor().newInstance();
}
}catch (Exception e){
e.printStackTrace();
}
return null;
}
}

优化客户端代码:

public static void main(String[] args) {
CourseFactory factory = new CourseFactory();
ICourse course = factory.create(JavaCourse.class);
course.record();
}

简单工厂模式的JDK实例

Calendar中的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 (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;
}

还有在logback中也使用到了简单工厂模式。
简单工厂模式的缺点就是工厂类的职责相对过重,不易于扩展复杂的产品结构。

工厂方法模式

工厂方法模式是指定义一个创建对象的接口,但让这个接口的实现类来决定实例化哪个类,工厂方法模式让类的实例化推迟到子类中进行。
在工厂方法模式中用户只需要关心所需产品对应的工厂,无需关心创建细节,而且加入新的产品时符合开闭原则。
工厂方法模式主要解决产品的扩展问题,在简单工厂模式中随着产品链的丰富,如果每个课程的创建逻辑有区别,则工厂的职责会变得越来越多,不利于维护。
根据单一职责原则我们将职能继续拆分,Java课程由Java工厂创建,MySql课程由MySQL工厂创建,对工厂本身也做一个抽象。
先创建一个ICourseFactory接口:

public interface ICourseFactory {
ICourse create();
}

再分别创建子工厂:

public class JavaCourseFactory implements ICourseFactory {
@Override
public ICourse create() {
return new JavaCourse();
}
}
public class MySQLCourseFactory implements ICourseFactory{
@Override
public ICourse create() {
return new MySQLCourse();
}
}

测试代码如下:

public static void main(String[] args) {
ICourseFactory factory=new JavaCourseFactory();
ICourse course = factory.create();
course.record();

factory=new MySQLCourseFactory();
course = factory.create();
course.record();
}

适合场景

工厂方法模式适合:
(1)创建对象需要大量重复代码
(2)客户端(应用层)不依赖于产品实例如何被创建、如何被实现等细节
(3)一个类通过其子类来指定创建哪个对象
缺点:
(1)类的个数容易过多,增加复杂度
(2)增加了系统的抽象性和理解难度

抽象工厂模式

抽象工厂模式是指提供一个创建一系列相关或相互依赖对象的接口,无需指定它们的具体类。
客户端(应用层)不依赖于产品类实例如何被创建如何被实现等细节,强调的是一系列相关的产品对象(属于同一产品族)一起使用创建对象需要大量重复代码。需要提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖具体实现。

讲解抽象工厂模式之前,我们先学习两个概念:产品等级结构和产品族。产品族就相当于移动、联通、电信之间的关系,产品等级就相当于移动品牌中的手机、路由器、笔记本之间的关系。

继续以课程举例,假设除了课堂视频外还需要提供课堂笔记,在产品等级中增加两个产品IVideo和INote:

public interface IVideo {
void record();
}
public interface INote {

void edit();
}

然后创建一个抽象工厂接口CourseFactory:

//抽象工厂是用户的主入口,是Spring中应用的最广泛的设计模,易于扩展
public interface CourseFactory {
INote createNote();
IVideo createVideo();
}

接下来,创建Java产品族的Java视频类和Java笔记类:

public class JavaVideo implements IVideo{
@Override
public void record() {
System.out.println("录制Java视频");
}
}
public class JavaNote implements INote {
@Override
public void edit() {
System.out.println("编写Java笔记");
}
}

创建Java产品族的具体工厂JavaCourseFactory:

public class JavaCourseFactory implements CourseFactory {

@Override
public INote createNote() {
return new JavaNote();
}

@Override
public IVideo createVideo() {
return new JavaVideo();
}
}

然后创建MySQL产品的视频类和笔记类:

public class MySQLVideo implements IVideo {
@Override
public void record() {
System.out.println("录制MySQL课程");
}
}
public class MySQLNote implements INote {
@Override
public void edit() {
System.out.println("编写MySQL笔记");
}
}

创建MySQL产品族的具体工厂MySQLCourseFactory:

public class MySQLCourseFactory implements CourseFactory {
@Override
public INote createNote() {
return new MySQLNote();
}

@Override
public IVideo createVideo() {
return new MySQLVideo();
}
}

客户端调用代码:

public class Test {

public static void main(String[] args) {
JavaCourseFactory factory = new JavaCourseFactory();
factory.createNote().edit();
factory.createVideo().record();
}
}

结构图:

上面的代码完整描述了两个产品族Java和MySQL,也描述了两个产品等级视频和笔记。抽象工厂模式完美清晰地描述了这样一层复杂的关系。

但是如果我们继续要扩展产品等级,将源码Source也加入课程,那么我们的代码要从抽象工厂全部调整,很显然不符合开闭原则。由此可知抽象工厂模式也是有缺点的:①规定了所有可能被创建的产品集合,产品族中扩展新的产品困难,需要修改抽象工厂接口。②增加了系统抽象性和理解难度。

但在实际开发中,我们我们可以根据情况不遵循开闭原则,代码每半年或一年升级一次未尝不可。

利用工厂模式重构

通过一个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;

//私有构造方法 不允许外部访问
private Pool(){
try{
init();
loadDrivers(driverName);
}catch (Exception e){
e.printStackTrace();
}
}

//初始化配置
private void init() throws IOException {
InputStream is = Pool.class.getResourceAsStream(propertiesName);
Properties properties = new Properties();
properties.load(is);
this.driverName=properties.getProperty("driverName");
this.maxConnect=Integer.parseInt(properties.getProperty("maxConnect"));
this.normalConnect=Integer.parseInt(properties.getProperty("normalConnect"));
}

//装载和注册所有JDBC驱动程序
private void loadDrivers(String driverName){
try{
driver= (Driver) Class.forName(driverName).getConstructor().newInstance();
DriverManager.registerDriver(driver);
System.out.println("成功注册JDBC驱动程序"+ driverName);
}catch (Exception e){
System.out.println("无法注册JDBC驱动程序"+ driverName +",错误:"+e);
}
}

//创建连接池
public abstract void createPool();

//单例模式返回数据库连接池实例
public static synchronized Pool getInstance() throws Exception{
if(instance!=null)
instance= (Pool) Class.forName("org.jdbc.sqlhelper.Pool").getConstructor().newInstance();
return instance;
}

//获得一个可用连接,如果没有则创建,且小于最大连接限制
public abstract Connection getConnection();

//获得一个连接,有时间限制
public abstract Connection getConnection(long time);

//将连接对象返回给连接池
public abstract void freeConnection(Connection connection);

//返回当前空闲连接数
public abstract int getNum();

//返回当前工作连接数
public abstract int getNumActive();

//撤销驱动
protected synchronized void release(){
try{
DriverManager.deregisterDriver(driver);
System.out.println("撤销JDBC驱动"+driver.getClass().getName());
}catch (Exception e){
System.out.println("无法撤销JDBC驱动"+driver.getClass().getName());
}
}
}

DBConnectionPool数据库连接池:

public class DBConnectionPool extends Pool {

//正在使用的连接数
private int checkOut;
//存放产生的连接对象容器
private Vector<Connection> freeConnections=new Vector<>();
//数据库连接信息
private String url;
private String userName;
private String passWord;
//空闲连接数
private static int num;
//当前可用连接数
private static int numActive=0;
//连接池实例
private static DBConnectionPool pool=null;

//产生数据库连接池
private DBConnectionPool(){
try {
init();
for(int i=0;i<normalConnect;i++){
Connection connection=newConnection();
if(connection!=null){
freeConnections.addElement(connection);//往容器中添加连接对象
num++;//记录总连接数
}
}
}catch (Exception e){
e.printStackTrace();
}
}

//获得一个数据库连接池实例
public static synchronized DBConnectionPool getInstance(){
if(pool==null)
pool=new DBConnectionPool();
return pool;
}

//初始化
private void init() throws IOException{
InputStream is = Pool.class.getResourceAsStream(propertiesName);
Properties properties = new Properties();
properties.load(is);
this.userName=properties.getProperty("userName");
this.passWord=properties.getProperty("passWord");
this.driverName=properties.getProperty("driverName");
this.url=properties.getProperty("url");
this.maxConnect=Integer.parseInt(properties.getProperty("maxConnect"));
this.normalConnect=Integer.parseInt(properties.getProperty("normalConnect"));
}

//如果不再使用某个连接对象,释放其到连接池
public synchronized void freeConnection(Connection connection){
freeConnections.addElement(connection);
num++;
checkOut--;
numActive--;
notifyAll();
}

//创建一个新连接
private Connection newConnection(){
Connection connection=null;
try{
if(userName==null)
connection= DriverManager.getConnection(url);
else
connection=DriverManager.getConnection(url,userName,passWord);
System.out.println("连接池创建一个新的连接");
}catch (Exception e){
System.out.println("无法创建这个url的连接"+url);
return null;
}
return connection;
}

//返回空连接
public int getNum(){
return num;
}

//返回当前连接数
public int getNumActive(){
return numActive;
}

//单例模式获取可用连接
public synchronized Connection getConnection(){
Connection connection=null;
if(freeConnections.size()>0){
num--;
connection=freeConnections.firstElement();
freeConnections.remove(0);
try{
if(connection.isClosed()){
System.out.println("从连接池中删除一个无效连接");
connection=getConnection();
}
}catch (Exception e){
System.out.println("从连接池中删除一个无效连接");
connection=getConnection();
}
}else if(maxConnect==0||checkOut<maxConnect) {//没有空闲连接且当前连接小于最大允许值 最大值为0则不限制
connection = newConnection();
}
if(connection!=null)
checkOut++;
numActive++;
return connection;
}

//获取一个连接,加上等待时间限制,单位ms
public synchronized Connection getConnection(long timeout){
long startTime=new Date().getTime();
Connection connection;
while ((connection=getConnection())==null){
try{
wait(timeout);
}catch (Exception ignored){}
if(new Date().getTime()-startTime>=timeout)
return null;
}
return connection;
}

//关闭所有连接
public synchronized void release(){
try{
Enumeration<Connection> allConnections = freeConnections.elements();
while (allConnections.hasMoreElements()){
Connection connection=allConnections.nextElement();
try{
connection.close();
num--;
}catch (SQLException e){
System.out.println("无法关闭连接");
}
}
}finally {
super.release();
}
}

//建立连接池
public void createPool(){
pool=new DBConnectionPool();
if(pool!=null)
System.out.println("连接池创建成功");
else
System.out.println("连接池创建失败");
}
}
  • 点赞
  • 收藏
  • 分享
  • 文章举报
要学好Java 发布了92 篇原创文章 · 获赞 430 · 访问量 8万+ 私信 关注
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: