【Java】初探工廠模式

導讀:工廠模式(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();
}

只不過是反射工廠對象罷了。 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章