引入
- 假設有一個司機, 需要到某個城市, 於是我們給他一輛汽車
public class Demo {
public static void main(String[] args) {
Car car = new Car();
car.run();
}
}
public class Car {
public void run(){
System.out.println("汽車正在向前跑...");
}
}
複製程式碼
- 如果我們希望給到這個司機的始終是一輛車, 應該怎麼做? (單例)
- 首先我們不能讓司機自己通過
new
產生一輛汽車, 而是應該通過呼叫Car
類中的某個方法對外提供車.
public class Car {
private static Car car = new Car();//用於提供給外界, 始終是同一輛車
private Car(){};//私有構造方法, 在類之外不能通過new獲得本類物件了, 保證了單例
public Car getInstance(){
return car;
}
public void run(){
System.out.println("汽車正在向前跑...");
}
}
public static void main(String[] args) {
Car car = Car.getInstance();
car.run();
}
複製程式碼
簡單工廠
- 下面考慮, 如果我們不希望只有汽車這種交通工具, 我們希望可以定製交通工具, 並定製生產交通工具的流程, 應該怎麼做?
- 一旦產生由汽車到交通工具這樣的概念, 就應該想到多型. 我們可以定義一個
Moveable
介面, 在介面中宣告run()
方法, 所有的交通工具類都實現該介面. - 對於定製生產流程, 我們可以通過一個工廠進行生產對應的交通工具.
public interface Moveable {
void run();
}
public class Car implements Moveable{
public Car(){};//私有構造方法, 在類之外不能通過new獲得本類物件了, 保證了單例
public void run(){
System.out.println("汽車正在向前跑...");
}
}
public abstract class VehicleFactory {
public abstract Moveable create();
}
public class CarFactory extends VehicleFactory {
@Override
public Moveable create() {
return new Car();
}
}
//Test
public static void main(String[] args) {
VehicleFactory factory = new CarFactory();
Moveable m = factory.create();
m.run();
}
複製程式碼
抽象工廠
- 下面把簡單工廠的畫面從腦海中清空, 講述另一種工廠實現.
- 我們假設開頭的司機不是一個普通的司機, 他除了需要一種交通工具以到達某個城市外, 他還需要一把AK47, 並且還需要一個蘋果以備路上不時之需.
- 所以我們需要給他一個工廠來製造這一系列產品.
- 為了提高可擴充套件性, 我們還希望不同的工廠可以製作不同系列的產品, 比如上面說的A工廠製造的是汽車, AK47, 蘋果; 而B工廠製造的是飛機, 火箭炮, 旺仔小饅頭.
//test
public static void main(String[] args) {
AbstractFactory factory = new Factory1();
Vehiche v = factory.createVehiche();
Weapon w = factory.createWeapon();
Food f = factory.createFood();
v.run();
w.fire();
f.eat();
}
public abstract class Vehiche {//交通工具的抽象類
public abstract void run();
}
public abstract class Weapon {//武器的抽象類
public abstract void fire();
}
public abstract class Food {//食物的抽象類
public abstract void eat();
}
public class Car extends Vehiche{一種具體的交通工具
@Override
public void run() {
System.out.println("小汽車啟動...");
}
}
public class AK47 extends Weapon {//一種具體的武器
@Override
public void fire() {
System.out.println("噠噠噠...");
}
}
public class Apple extends Food{//一種具體的食物
@Override
public void eat() {
System.out.println("大口吃蘋果...");
}
}
//抽象工廠
public abstract class AbstractFactory {
public abstract Vehiche createVehiche();
public abstract Weapon createWeapon();
public abstract Food createFood();
}
//抽象工廠的實現1
public class Factory1 extends AbstractFactory {
@Override
public Vehiche createVehiche() {
return new Car();
}
@Override
public Weapon createWeapon() {
return new AK47();
}
@Override
public Food createFood() {
return new Apple();
}
}
複製程式碼
- 總結一下, 抽象工廠和簡單工廠各有什麼優劣?
- 抽象工廠能夠生產一系列產品, 也能方便地替換掉一系列產品, 但是如果想要在產品系列中新增多一個品種將會非常麻煩. 比如說在上面的系列產品中新增一個
盔甲
抽象類, 那麼抽象工廠以及對應的實現都要修改原始碼了. - 而簡單工廠能夠靈活的生產但一個品種的產品, 但是如果生產的品種較多, 會出現工廠氾濫的問題.
- 兩者優劣互補, 那麼有沒有可以相容兩者優點的工廠實現呢? 下面看
spring
的工廠實現, 它給出了一種解決方案.
Spring的bean工廠
- 我們再次考慮最原始的情況, 有一個
Moveable
介面, 裡面有run
方法,Car
小汽車類實現了該介面.
public static void main(String[] args) {
Moveable m = new Car();
m.run();
}
public interface Moveable {
void run();
}
public class Car implements Moveable{
@Override
public void run() {
System.out.println("小汽車往前跑...");
}
}
複製程式碼
- 在Spring的bean工廠中, 新物件不是通過
new
關鍵字獲取的, 而是通過配置檔案獲取的. - 具體的過程是: 先讀取配置檔案獲得該類的
class
物件, 然後通過class
物件建立具體的例項物件.
public static void main(String[] args) throws Exception {
//獲取配置檔案
Properties props = new Properties();
props.load(Test.class.getClassLoader().getResourceAsStream("spring.properties"));
//獲取配置檔案中配置的類
String vehicheTypeName = props.getProperty("vehicheTypeName");
//反射生成對應的物件
Moveable m = (Moveable) Class.forName(vehicheTypeName).newInstance();
m.run();
}
//spring.properties
vehicheTypeName=designPattern.factory.springFactory.Car
複製程式碼
- 上面是對
spring
中bean工廠使用的模擬, 下面我們使用真實的spring
來生成Car
物件, 對比一下.
public static void main(String[] args) throws Exception {
BeanFactory bf = new ClassPathXmlApplicationContext("applicationContext.xml");
Vehiche v = (Vehiche)bf.getBean("v");
v.run();
}
//配置檔案
<bean id="v" class="designPattern.factory.Car">
</bean>
複製程式碼
- 經過對比我們發現我們自己寫的簡單工廠和spring的bean工廠在使用上沒有什麼區別, 確實
spring
使用起來就是這麼簡單, 下面我們模擬一下spring的bean工廠實現.
模擬Spring工廠實現
模擬IOC
- 都說
spring
是個bean
容器, 以下的程式碼將展示它是如何生成bean
, 並把bean
放入容器中供使用者獲取的. - 思路比較簡單:
- 建立
BeanFactory
工廠介面, 新增方法getBean()
. - 建立
BeanFactory
的實現類ClassPathXmlApplicationContext
. 將在該實現類中展示IOC的具體實現. ClassPathXmlApplicationContext
需要一個container
容器存放建立的bean物件, 這裡使用HashMap
實現.- 在
ClassPathXmlApplicationContext
的構造方法中讀取spring
的配置檔案, 這裡使用到了dom4j
. 讀取配置檔案後根據bean
的class
屬性使用反射建立出bean
物件. 然後把id
和bean
物件分別作為key
和value
新增到容器中. - 當工廠被呼叫
getBean()
方法時, 從容器中找到對應的bean
並返回.
public static void main(String[] args) throws Exception {
BeanFactory bf = new ClassPathXmlApplicationContext("applicationContext.xml");
Vehiche v = (Vehiche) bf.getBean("v");
v.run();
}
//BeanFactory的實現類
public class ClassPathXmlApplicationContext implements BeanFactory {
private Map<String, Object> container = new HashMap<>();//用於存放bean物件的容器
//在構造方法中讀取xml配置檔案, 把bean物件都建立好並放入容器中
public ClassPathXmlApplicationContext(String propAddr) throws Exception {
SAXReader reader = new SAXReader();
File file = new File(this.getClass().getClassLoader().getResource(propAddr).toURI());
Document document = reader.read(file);
Element root = document.getRootElement();
List<Element> childElements = root.elements();
for (Element child : childElements) {
Object bean = Class.forName(child.attributeValue("class")).newInstance();
container.put(child.attributeValue("id"), bean);
}
}
@Override
public Object getBean(String beanId) {
return container.containsKey(beanId) ? container.get(beanId) : null;
}
}
//極簡BeanFactory
public interface BeanFactory {
Object getBean(String beanId);
}
//xml中配置的bean
<bean id="v" class="designPattern.factory.Car">
</bean>
複製程式碼