导读:工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种设计模式提供了一种不同于古老的new方式来创建对象,能够增加程序的可维护性、复用性,便于修改。
假设有三个课程类,英语、数学、物理,每个类只有一个属性,即课程名。现在有个“选课”的方法,输出某某同学选择了某门课。
public class Function {
public static void chooseCourse(String name,String student){
if(name.equals("English")){
English english=new English();
System.out.println(student+"选择了"+english.getName()+"课");
}else if (name.equals("Math")){
Math math = new Math();
System.out.println(student+"选择了"+math.getName()+"课");
}else if (name.equals("Physics")){
Physics physics=new Physics();
System.out.println(student+"选择了"+physics.getName()+"课");
}
}
}
再主函数中传入“课程名”和“学生”,“张三选择了数学课”。
public class test {
public static void main(String[] args) {
Function.chooseCourse("Math","张三");
}
}
这种通过new一个对象的方法,有个弊端,就是当类增多时(课程增多时),这个if判断将会十分庞大,维护起来十分麻烦。
问题①:不过在这之前,上面的if判断,虽然都是不同类的对象,但都有一个共同的逻辑,即输出某某选择了“某门课”,那能不能把这个逻辑抽取出来呢?
1.问题①的解决:抽取共同逻辑
抽取共同逻辑,不就把这个输出语句拿出来不就行了嘛:
public class Function {
public static void chooseCourse(String name,String student){
if(name.equals("English")){
English english=new English();
}else if (name.equals("Math")){
Math math = new Math();
}else if (name.equals("Physics")){
Physics physics=new Physics();
}
System.out.println(student+"选择了"+physics.getName()++"课");
}
}
但问题又来了,由于要调用某个对象的getName方法,还是需要知道具体是哪个对象。怎么解决呢?干脆定义一个变量,根据不同的情况,来赋值就行了:
public static void chooseCourse(String name,String student){
String courseName=null;
if(name.equals("English")){
English english=new English();
courseName=english.getName();
}else if (name.equals("Math")){
Math math = new Math();
courseName=math.getName();
}else if (name.equals("Physics")){
Physics physics=new Physics();
courseName=physics.getName();
}
System.out.println(student+"选择了"+courseName);
}
问题②:上面看似解决了问题,实则不然。目前的课程只有一个属性,自然只需要一个变量来取这个属性,但当课程有N个属性的时候,就需要N个变量来取,整个if判断仍然十分繁琐。比如当增加学分和课时属性的时候,就会变成这样:
public static void chooseCourse(String name,String student){
String courseName=null;
Integer courseCredits=null;
Integer courseHours=null;
if(name.equals("English")){
English english=new English();
courseName=english.getName();
courseCredits=english.getCredits();
courseHours=english.getHours();
}else if (name.equals("Math")){
Math math = new Math();
courseName=math.getName();
courseCredits=math.getCredits();
courseHours=math.getHours();
}else if (name.equals("Physics")){
Physics physics=new Physics();
courseName=physics.getName();
courseCredits=physics.getCredits();
courseHours=physics.getHours();
}
System.out.println(student+"选择了"+courseName+"课,该课有"+courseCredits+"个学分,一共"+courseHours+"个学时");
}
2.问题②的解决:通过接口或者父类来处理
既然每个对象,都有Name和Credits属性,那么干脆用一个Course接口,提供getName或者getCredits方法,让具体的课程自己去实现就行了。
public interface Course {
public String getName();
public Integer getCredits();
}
public class English implements Course {
private String name;
private Integer credits;
public English(){
this.name="英语";
this.credits=2;
}
//getter and setter
}
这样,上面的if又变简洁了不少。
public static void chooseCourse(String name,String student){
Course course=null;
if(name.equals("English")){
course=new English();
}else if (name.equals("Math")){
course = new Math();
}else if (name.equals("Physics")){
course=new Physics();
}
System.out.println(student+"选择了"+course.getName()+"课,此课有"+course.getCredits()+"个学分");
}
}
问题③:但是,上面仅仅是对添加属性有效。如果要添加新的类呢?比如现在有体育、音乐、化学、生物,那这个if又会变得十分繁琐。并且,如果有了新的业务场景,比如还有学生退课、老师授课等新方法。总之,只要有创建课程对象的地方,都需要添加,维护和扩展很难。
3.问题③的解决:简单工厂
对于上面的弊端,我们可以把“创建对象”的过程封装起来,需要创建的时候,直接调用创建工厂的方法就行了。
创建一个简单工厂类,将if判断封装到这个类中,用来创建各种课程的对象:
public class SimpleFactory {
public static Course createCourse(String name){
Course course=null;
if(name.equals("English")){
course=new English();
}else if (name.equals("Math")){
course = new Math();
}else if (name.equals("Physics")){
course=new Physics();
}
return course;
}
}
这样,chooseCourse方法就会变得十分简洁。
改进①:但这样,也有一个细节的问题。目前,所有课程判断的时候,都是手动输入的;主函数调用的时候,也是手动输入的,容易打错,维护性也低。
public static void chooseCourse(String name,String student){
Course course= SimpleFactory.createCourse(name);
System.out.println(student+"选择了"+course.getName()+"课,此课有"+course.getCredits()+"个学分");
}
public class test {
public static void main(String[] args) {
Function.chooseCourse("Math","张三");
Function.chooseCourse("English","李四");
Function.chooseCourse("Physics","王五");
}
}
4.改进①:创建常量
创建一个常量类:
public class CourseConstant {
public static final String ENGLISH_NAME="English";
public static final String MATH_NAME="Math";
public static final String PHYSICS_NAME="Physics";
}
然后在上面的“简单工厂”类中修改:
//public class SimpleFunctory
public static Course createCourse(String name){
Course course=null;
if(name.equals(CourseConstant.ENGLISH_NAME)){
course=new English();
}else if (name.equals(CourseConstant.MATH_NAME)){
course = new Math();
}else if (name.equals(CourseConstant.PHYSICS_NAME)){
course=new Physics();
}
return course;
}
//public Class test
public static void main(String[] args) {
Function.chooseCourse(CourseConstant.ENGLISH_NAME,"张三");
Function.chooseCourse(CourseConstant.MATH_NAME,"李四");
Function.chooseCourse(CourseConstant.PHYSICS_NAME,"王五");
}
改进②:上面的创建过程,其实还可以使用Java的反射机制来进一步进化。
5.改进②:使用Java反射来创建
在SimpleFactory添加一个新方法:
//public class SimpleFactory
public static Course createCourseByName(String name) throws Exception{
return (Course)Class.forName(name).newInstance();
}
修改常量类:
public class CourseConstant {
public static final String ENGLISH_NAME="course.English";
public static final String MATH_NAME="course.Math";
public static final String PHYSICS_NAME="course.Physics";
修改创建的方式:
//public class Function
public static void chooseCourse(String name,String student) throws Exception {
Course course= SimpleFactory.createCourseByName(name);
System.out.println(student+"选择了"+course.getName()+"课,此课有"+course.getCredits()+"个学分");
}
这样,就进一步简化了。 但是问题又来了。
问题④:上面每个课程的属性,都比较简单,只是一个单纯的数据结构。如果属性里面还包含其它类,比如有授课老师这个属性,助教这个属性,课代表这个属性,那么情况又会复杂起来。
新建一个Component包,在里面创建三个类,分别是Teacher、TA和ClassRepr,每个类目前只有一个属性,即name。
那么现在English和其它课程类,就要添加这些属性。
public class English implements Course {
private String name;
private Integer credits;
private Teacher teacher;
private ClassRepr classRepr;
//getter and setter
}
public class Math implements Course {
private String name;
private Integer credits;
private Teacher teacher;
private TA ta;
private ClassRepr classRepr;
//getter and setter
}
接下来,在简单工厂类里面:
public static Course createCourse(String name){
Course course=null;
if(name.equals(CourseConstant.ENGLISH_NAME)){
English english=new English();
Teacher teacher=new Teacher();
teacher.setName("刘老师");
english.setTeacher(teacher);
ClassRepr classRepr=new ClassRepr();
classRepr.setName("李同学");
english.setClassRepr(classRepr);
course=english;
}else if (name.equals(CourseConstant.MATH_NAME)){
Math math = new Math();
Teacher teacher=new Teacher();
teacher.setName("何老师");
math.setTeacher(teacher);
TA ta=new TA();
ta.setName("郭同学");
math.setTa(ta);
ClassRepr classRepr=new ClassRepr();
classRepr.setName("贺同学");
math.setClassRepr(classRepr);
course=math;
}else if (name.equals(CourseConstant.PHYSICS_NAME)){
Physics physics=new Physics();
Teacher teacher=new Teacher();
teacher.setName("何老师");
physics.setTeacher(teacher);
TA ta=new TA();
ta.setName("郭同学");
physics.setTa(ta);
ClassRepr classRepr=new ClassRepr();
classRepr.setName("贺同学");
physics.setClassRepr(classRepr);
course=physics;
}
return course;
}
代码量陡然增加! 而且之间的反射方法,也不能用了。除此之外,代码复用性和扩展性也变差了很多,不符合开闭原则(修改跟扩展的分离),无论是修改还是扩展还是复用,都需要在上面的代码做修改。
6.问题④的解决:工厂方法
解决的思路就是把每一个类的创建都交给一个工厂类,而不是用一个工厂类来一揽子创建所有不同的类。
创建三个相应的工厂类:
public class EnglishFactory {
public Course createCourse(){
English english=new English();
Teacher teacher=new Teacher();
teacher.setName("刘老师");
english.setTeacher(teacher);
ClassRepr classRepr=new ClassRepr();
classRepr.setName("李同学");
english.setClassRepr(classRepr);
return english;
}
}
public class MathFactory {
public Course createCourse(){
Math math = new Math();
Teacher teacher=new Teacher();
teacher.setName("何老师");
math.setTeacher(teacher);
TA ta=new TA();
ta.setName("郭同学");
math.setTa(ta);
ClassRepr classRepr=new ClassRepr();
classRepr.setName("贺同学");
math.setClassRepr(classRepr);
return math;
}
}
public class PhysicsFactory {
public Course createCourse(){
Physics physics=new Physics();
Teacher teacher=new Teacher();
teacher.setName("何老师");
physics.setTeacher(teacher);
TA ta=new TA();
ta.setName("郭同学");
physics.setTa(ta);
ClassRepr classRepr=new ClassRepr();
classRepr.setName("贺同学");
physics.setClassRepr(classRepr);
return physics;
}
}
由于每个工厂都有createCourse方法,还需要封装到一个接口里面:
public interface Factory {
public Course createCourse();
}
再让每个工厂实现这个接口。
同时还需要编写一个工厂创建的类,用来判断到底用哪个工厂进行创建:
public class FactoryBuilder {
public static Factory build(String name){
Factory factory=null;
if(name.equals(CourseConstant.ENGLISH_NAME)){
factory=new EnglishFactory();
}else if (name.equals(CourseConstant.MATH_NAME)){
factory = new MathFactory();
}else if (name.equals(CourseConstant.PHYSICS_NAME)){
factory=new PhysicsFactory();
}
return factory;
}
}
然后进行调用:
public class Function {
public static void chooseCourse(String name,String student) throws Exception {
Factory factory= FactoryBuilder.build(name);
Course course=factory.createCourse();
System.out.println(student+"选择了"+course.getName()+"课,此课有"+course.getCredits()+"个学分");
}
}
这样,如果有新的课程,直接创建新的课程工厂实现工厂接口,添加一条创建判断就行了。
同时,上面的FactoryBuilder又可以用反射来处理了:
public static Factory buildByClassName(String name) throws Exception{
return (Factory)Class.forName(name).newInstance();
}
只不过是反射工厂对象罢了。