導讀:工廠模式(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();
}
只不過是反射工廠對象罷了。