工厂模式
一、简单工厂模式
实例化对象的时候不再使用 new Object()形式,可以根据用户的选择条件来实例化相关的类。
对于客户端来说,去除了具体的类的依赖。只需要给出具体实例的描述给工厂,工厂就会自动返回具体的实例对象。
具体实现如下(需求:加减乘除):
0、类图
1. 定义一个操作接口:
/** * 定义一个操作接口 */ public interface Operation { public double getResult(double numberA,double numberB) throws Exception; }
2. 定义具体的操作类:
/** * 加法计算 */ public class Add implements Operation { @Override public double getResult(double numberA, double numberB) throws Exception { return numberA + numberB; } }
/** * 减法计算 */ public class Sub implements Operation { @Override public double getResult(double numberA, double numberB) throws Exception { return numberA - numberB; } }
/** * 乘法计算 */ public class Mul implements Operation { @Override public double getResult(double numberA, double numberB) throws Exception { return numberA * numberB; } }
/** * 除法计算 */ public class Div implements Operation { @Override public double getResult(double numberA, double numberB) throws Exception { return numberA / numberB; } }
3. 定义简单工厂类:
/** * 使用这种模式,我们在生成工厂的时候可以加一些业务代码,如日志、判断业务等,这时候可以直接在switch case中加上去就行了 * 这样做的优点:我们可以对创建的对象进行一些 “加工” ,而且客户端并不知道,因为工厂隐藏了这些细节。 * 如果,没有工厂的话,那我们是不是就得自己在客户端上写这些代码,这就好比本来可以在工厂里生产的东西,拿来自己手工制作,不仅麻烦以后还不好维护。 * * 缺点也很明显:(1)如果需要在方法里写很多与对象创建有关的业务代码,而且需要的创建的对象还不少的话, * 我们要在这个简单工厂类里编写很多个方法,每个方法里都得写很多相应的业务代码,这会导致这个简单工厂类很庞大臃肿、耦合性高, * (2)每次增加、删除某个子类对象的创建都需要打开简单工厂类来进行修改代码,也违反了开-闭原则。 */ public class SimpleFactory { private static Operation operationObj = null; private static Operation add(){ System.out.print("加法运算:"); return new Add(); } private static Operation sub(){ System.out.print("减法运算:"); return new Sub(); } private static Operation mul(){ System.out.print("乘法运算:"); return new Mul(); } private static Operation div(){ System.out.print("除法运算:"); return new Div(); } //简单工厂,根据字符串(调用对应的方法)创建相应的对象 public static Operation createOperation(String name){ switch(name){ case "+": operationObj = add(); break; case "-": operationObj = sub(); break; case "*": operationObj = mul(); break; case "/": operationObj = div(); break; default: break; } return operationObj; } }
4. 客户端代码:
/** * Created by Timetellu on 2019/10/3. * 对于客户端来说,去除了具体的类的依赖。只需要给出具体实例的描述给工厂,工厂就会自动返回具体的实例对象。 * 客户端: 无需提供具体的子类类名,只需要提供一个字符串即可得到相应的实例对象。 * 这样的话,当子类的类名更换或者增加子类时我们都无需修改客户端代码,只需要在简单工厂类上增加一个分支判断代码即可。 * */ public class Client { public static void main(String[] args) throws Exception { Operation add = SimpleFactory.createOperation("+"); System.out.println(add.getResult(1,1)); Operation sub = SimpleFactory.createOperation("-"); System.out.println(sub.getResult(1,1)); Operation mul = SimpleFactory.createOperation("*"); System.out.println(mul.getResult(1,1)); Operation div = SimpleFactory.createOperation("/"); System.out.println(div.getResult(1,1)); } }
二、工厂方法模式(抽象工厂模式的特例)
如果需要在方法里写很多与对象创建有关的业务代码,而且需要的创建的对象还不少的话,我们要在这个简单工厂类里编写很多个方法,每个方法里都得写很多相应的业务代码,而每次增加子类或者删除子类对象的创建都需要打开这简单工厂类来进行修改。
这会导致这个简单工厂类很庞大臃肿、耦合性高,而且增加、删除某个子类对象的创建都需要打开简单工厂类来进行修改代码也违反了开-闭原则。
这时候就需要使用工厂模式了。工厂方法模式是对简单工厂模式进一步的解耦,因为在工厂方法模式中是一个子类对应一个工厂类,而这些工厂类都实现于一个抽象接口。这相当于是把原本会因为业务代码而庞大的简单工厂类,拆分成了一个个的工厂类,这样代码就不会都耦合在同一个类里了。
0、类图
1. 定义一个工厂接口:
/** * 定义一个工厂接口:用来创建operation工厂 */ public interface Factory { public Operation createOperation(); }
2. 定义具体的操作工厂:
/** * 加法工厂:创建具体的加法操作 */ public class AddFactory implements Factory { @Override public Operation createOperation() { System.out.print("加法运算:"); return new Add(); } }
/** * 减法工厂:创建具体的减法操作 */ public class SubFactory implements Factory { @Override public Operation createOperation() { System.out.print("减法运算:"); return new Sub(); } }
/** * 乘法工厂:创建具体的乘法操作 */ public class MulFactory implements Factory { @Override public Operation createOperation() { System.out.print("乘法运算:"); return new Mul(); } }
/** * 除法工厂:创建具体的除法操作 */ public class DivFactory implements Factory { @Override public Operation createOperation() { System.out.print("除法运算:"); return new Div(); } }
3. Operation操作接口和具体的操作实现类,同上
4.工厂主管(客户端操作接口):
/** * 工厂主管:根据用户输入进行统筹分配不同工厂生产 */ public class MethodFactory { private static Factory FactoryObj = null; public static Factory createFactory(String name){ switch (name) { case "+": FactoryObj = new AddFactory(); break; case "-": FactoryObj = new SubFactory(); break; case "*": FactoryObj = new MulFactory(); break; case "/": FactoryObj = new DivFactory(); break; } return FactoryObj; } }
5. 客户端代码:
/** * 客户端 * 工厂方法模式总结: * 优点:想要加功能,本来是改工厂类的,而现在是修改客户端。 * 而且各个不同功能的实例对象的创建代码,也没有耦合在同一个工厂类里,这也是工厂方法模式对简单工厂模式解耦的一个体现。 * 【工厂方法模式克服了简单工厂会违背开-闭原则的缺点,又保持了封装对象创建过程的优点。】 * 缺点:每增加一个产品类,就需要增加一个对应的工厂类,增加了额外的开发量。 */ public class Client { public static void main(String[] args) throws Exception { Factory addFactory = MethodFactory.createFactory("+"); Factory SubFactory = MethodFactory.createFactory("-"); Factory MulFactory = MethodFactory.createFactory("*"); Factory DivFactory = MethodFactory.createFactory("/"); double addResult = addFactory.createOperation().getResult(1, 2); System.out.println(addResult); double subResult = SubFactory.createOperation().getResult(1, 2); System.out.println(subResult); double mulResult = MulFactory.createOperation().getResult(1, 2); System.out.println(mulResult); double divResult = DivFactory.createOperation().getResult(1, 2); System.out.println(divResult); } }
三、抽象工厂模式
场景:对不同类型数据库中的表进行修改
3.1 先用工厂方法模式实现(一个“产品”)
1. 现在要对mysql/oracle数据库中的User表进行操作,User表定义如下:
package cn.timetell.factory.abstractFactory.domain; /** * 要对mysql/oracle数据库中的User表进行操作,User表定义如下: */ public class User { private int uid; private String uname; public User() { } public User(int uid, String uname) { this.uid = uid; this.uname = uname; } public int getUid() { return uid; } public void setUid(int uid) { this.uid = uid; } public String getUname() { return uname; } public void setUname(String uname) { this.uname = uname; } }
2. 接下来定义一个对User进行操作的接口:
/** * 定义一个对User进行操作的接口 */ public interface IUser { public void insert(User user); public User getUser(int uid); }
3. 实现一个对mysql中User进行操作的类:
/** * 实现一个对mysql中User进行操作的类: */ public class MysqlUser implements IUser { @Override public void insert(User user) { System.out.println("mysql中插入了一条数据"); } @Override public User getUser(int uid) { System.out.println("在mysql中,查询到了一条id为"+uid+"的数据"); return null; } }
实现对oracle中User进行操作的类:
/** * 实现一个对oracle中User进行操作的类: */ public class OracleUser implements IUser { @Override public void insert(User user) { System.out.println("oracle中插入了一条数据"); } @Override public User getUser(int uid) { System.out.println("在oracle中,查询到了一条id为"+uid+"的数据"); return new User(); } }
4. 接下来定义一个工厂接口,用于生产访问User表的对象:
/** * 定义一个工厂接口,用于生产访问User表的对象 */ public interface sqlFactory { public IUser createUser(); }
5. 生产mysqlUser对象的mysql工厂类:
/** * 生产MysqlUser对象的mysql工厂类 */ public class MysqlFactory implements sqlFactory { @Override public IUser createUser() { System.out.println("----mysql----"); return new MysqlUser(); } }
6. 用户测试类:
public class Client { public static void main(String[] args) { //要插入的对象 User insertUser = new User(1,"张三"); IUser queryUser = new MysqlFactory().createUser(); queryUser.insert(insertUser); queryUser.getUser(1); } }
3.2 在工厂方法基础上添加“产品”——> 抽象工厂模式
当数据库中不止一个表的时候该怎么解决问题呢,此时就可以引入抽象工厂模式了。
0. 类图
1. 比如说现在增加了一个Login类,用于记录登陆信息:
import java.util.Date; /** * Created by Timetellu on 2019/10/3. * 增加了一个Login类,用于记录登陆信息 */ public class Login { private int id; private Date date; public Login() { } public Login(int id, Date date) { this.id = id; this.date = date; } public int getId() { return id; } public void setId(int id) { this.id = id; } public Date getDate() { return date; } public void setDate(Date date) { this.date = date; } }
2. 此时就要相应地添加 对Login表操作的ILogin接口,MysqlLogin类,OracleLogin类:
/** * 相应地添加 对login表操作的ILogin接口 */ public interface ILogin { public void insert(Login login); public Login getLogin(int id); }
/** * MysqlLogin类 */ public class MysqlLogin implements ILogin { public void insert(Login login) { System.out.println("对 MySQL 里的 Login 表插入了一条数据"); } public Login getLogin(int id) { System.out.println("通过 uid 在 MySQL 里的 Login 表得到了一条数据"); return null; } }
/** * OracleLogin 类 */ public class OracleLogin implements ILogin { public void insert(Login login) { System.out.println("对 Oracle 里的 Login 表插入了一条数据"); } public Login getLogin(int id) { System.out.println("通过 uid 在 Oracle 里的 Login 表得到了一条数据"); return null; } }
3. 修改sqlFactory接口及sqlFactory实现类的内容:
/** * 定义一个工厂接口,用于生产访问User表的对象 * 修改Factory接口及Factory实现类的内容: 定义一个抽象的工厂接口,该工厂用于生产访问User表以及Login表的对象 */ public interface sqlFactory { public IUser createUser(); public ILogin createLogin(); }
/** * 生产MysqlUser对象的mysql工厂类 * 生产MysqlLogin对象的mysql工厂类 */ public class MysqlFactory implements sqlFactory { @Override public IUser createUser() { System.out.println("----mysql----"); return new MysqlUser(); } @Override public ILogin createLogin() { return new MysqlLogin(); } }
/** * 生产Oracleuser对象的oracle工厂类 * 生产OracleLogin对象的oracle工厂类 */ public class OracleFactory implements sqlFactory { @Override public IUser createUser() { System.out.println("----oracle----"); return new OracleUser(); } @Override public ILogin createLogin() { return new OracleLogin(); } }
4. 客户端代码:
/** * 客户端 * 抽象工厂模式总结 * 优点: * 1、抽象工厂模式最大的好处是易于交换产品系列,由于具体工厂类,例如 sqlFactory factory=new OracleFactory(); * 在一个应用中只需要在初始化的时候出现一次,这就使得改变一个应用的具体工厂变得非常容易,
它只需要改变具体工厂即可使用不同的产品配置(createUser、createLogin)。 * 2、抽象工厂模式的另一个好处就是它让具体的创建实例过程与客户端分离, * 客户端是通过它们的抽象接口操作实例,产品实现类的具体类名也被具体的工厂实现类分离,不会出现在客户端代码中。 * 缺点: 1. 如果你的需求来自增加功能,比如增加Login表,就有点太烦了: 首先需要增加 ILogin,MysqlLogin,OracleLogin。 然后还要去修改工厂类: sqlFactory, MysqlFactory, OracleFactory 才可以实现,
需要修改三个类,实在是有点麻烦。 2. 还有就是,客户端程序肯定不止一个,每次都需要声明sqlFactory factory=new MysqlFactory(), 如果有100个调用数据库的类,就需要更改100次sqlFactory factory=new oracleFactory()。 */ public class Client { public static void main(String[] args) { //要插入的对象 User insertUser = new User(1,"张三"); Login insertLogin = new Login(1,new Date()); IUser queryUser = new MysqlFactory().createUser(); queryUser.insert(insertUser); queryUser.getUser(1); ILogin queryLogin = new OracleFactory().createLogin(); queryLogin.insert(insertLogin); queryLogin.getLogin(1); } }
5. 与工厂方法模式比较:
四、改进抽象工厂模式(+反射+配置文件+简单工厂)
1. 配置文件:
//sqlServer.properties packageName=cn.timetell.factory.abstractFactory.operation.impl DBName=Mysql
2.工厂管理(客户端操作接口类):
import cn.timetell.factory.abstractFactory.operation.ILogin; import cn.timetell.factory.abstractFactory.operation.IUser; import java.io.BufferedInputStream; import java.io.IOException; import java.util.Properties; /** * 抽象工厂的改进(+反射+配置文件+简单工厂) * 使用反射的话,我们就可以不需要使用switch,因为使用switch的话,添加一个sql server数据库的话,又要switch的话又需要添加case条件。 (1)可以根据 选择的数据库名称,如 “mysql”, 利用反射技术自动的获得所需要的实例 (2)单使用反射技术的话,仍需要进SimpleFactory2中修改数据库类型,还不是完全符合开-闭原则。 可以通过配置文件来达到目的,每次通过读取配置文件来知道我们应该使用哪种数据库。 动态加载: Class.forName( )静态方法的目的是为了动态加载类 调用Class下的newInstance( )静态方法来实例化对象以便操作 */ public class SimpleFactory2 { private static String DBName = null; private static String packageName = null; static { //放在静态块中,在类加载时就 加载配置文件 Properties pro = new Properties(); BufferedInputStream bis = new BufferedInputStream(SimpleFactory2.class.getClassLoader().getResourceAsStream("sqlServer.properties")); try { pro.load(bis); packageName = pro.getProperty("packageName"); DBName = pro.getProperty("DBName"); } catch (IOException e) { e.printStackTrace(); } } public static IUser createUser() throws Exception{ String className = packageName+"."+DBName+"User"; //cn.timetell.factory.abstractFactory.operation.impl.MysqlUser return (IUser)Class.forName(className).newInstance(); } public static ILogin createLogin() throws Exception{ String className = packageName+"."+DBName+"Login"; return (ILogin)Class.forName(className).newInstance(); } }
3. 客户端代码:
/** * 客户端 */ public class Client2 { public static void main(String[] args) throws Exception { //查询User表 IUser user = SimpleFactory2.createUser(); user.insert(new User()); user.getUser(3); //查询Login表 ILogin login = SimpleFactory2.createLogin(); login.insert(new Login()); login.getLogin(2); } }
【参考】:
http://blog.51cto.com/zero01/2067822
http://blog.51cto.com/zero01/2070033