【分享】我在阿里工作十年里接觸過的Java框架設計模式
一、前言
說起來設計模式,大家應該都耳熟能詳,設計模式代表了軟件設計的最佳實踐,是經過不斷總結提煉出來的代碼設計經驗的分類總結,這些模式或者可以簡化代碼,或者可以是代碼邏輯開起來清晰,或者對功能擴展很方便…
設計模式按照使用場景可以分為三大類:創建型模式(Creational Patterns)、結構型模式(Structural Patterns)、行為型模式(Behavioral Patterns)。
-
創建型模式(Creational Patterns)
對對象的實例化過程進行抽象,這使得一個系統可以不用關心這些對象是如何創建,組合,呈現的,對于類創建模式來說通過使用繼承改變實例化的類,對于對象創建模式來說通過使用代理來實例化所需要的對象。
-
結構型模式(Structural Patterns)
通過對多個類和對象進行組合得到復雜結構的類,一般使用繼承繼承或者成員變量引用形式來實現。
-
行為型模式(Behavioral Patterns)
行為模式不僅表達了對象和類,還表達了他們之間的交互,涉及到了對象和算法的分配。
下面就帶大家看下開源框架框架中是如何應用這些經典設計模式的。
二、責任鏈設計模式(Chain of Responsibility Pattern)
2.1 介紹
責任鏈模式是把多個對象串聯起來形成一個鏈狀結構,讓每個對象都有機會對事件發送者的請求進行處理。責任鏈模式是設計模式中的行為模式,設計意圖是為了使事件發送者和事件接受者之間解耦。通常責任鏈鏈中的每個對象都有下一個對象的引入(例如tomcat 里面StandardPipeline用來管理valve),或者有個同一個鏈管理工廠里面使用數組存放了所有的對象(例如tomcat里面ApplicationFilterChain用來關系filter)。
2.2 Tomcat中Valve鏈
Tomcat中StandardEngine,StandardHost,StandardContext里面都有自己StandardPipeline,下面以StandardEngine里面StandardPipeline為例講解
從上面類圖可知道每個Valve都要繼承ValveBase類,該類里面有一個Valve的引用,實際是鏈中下一個節點對象,Valve就是通過每個Valve里面的next串聯為鏈的。
每個valve的invoke方法里面調用next.invoke激活鏈中下一個節點,并且StandardEngine,StandardHost,StandardContext都有一個basic valve這個valve在鏈的末尾用來激活子容器的valve鏈。
2.3 Tomcat中Filter鏈
Tomcat中Filter鏈是使用ApplicationFilterChain來管理的,具體結構如下圖:
可知Filter鏈不是像Valve一樣在內部維護下個節點的引用,而是在ApplicationFilterChain中搞了個數組存放所有的Filter,并通過n統計Filter總個數,pos是當前filter的下標。
ApplicationFilterChain的doFilter代碼如下:
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { ... internalDoFilter(request,response); ... }private void internalDoFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { // Call the next filter if there is one if (pos < n) { //獲取filter鏈中下標為pos的filter ApplicationFilterConfig filterConfig = filters[pos++]; Filter filter = null; try { filter = filterConfig.getFilter(); support.fireInstanceEvent(InstanceEvent.BEFORE_FILTER_EVENT, filter, request, response); if (request.isAsyncSupported() && "false".equalsIgnoreCase( filterConfig.getFilterDef().getAsyncSupported())) { request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE); } ... //調用自定義filter的dofilter方法 filter.doFilter(request, response, this); support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT, filter, request, response); } .... } .....}
2.4 使用場景
-
當一個請求需要根據請求參數的不同由不同對象來處理時候。
-
當一個請求需要固定對象順序處理,并且可擴展性的在固定順序里面插入新的對象進行處理時候。
三、工廠模式(Factory Pattern)
3.1 介紹
工廠模式是創建型模式,他封裝了對象的創建過程,調用者使用具體的工廠方法根據參數就可以獲取對應的對象。
3.2 Spring框架中BeanFactory
如圖BeanFactory接口提供了getBean方法,在AbstractBeanFactory中實現了該方法,經過層層繼承,實現,最后DefaultListableBeanFactory實現了BeanDefinitionRegistry接口用來保存bean定義,繼承了AbstractAutowireCapableBeanFactory用來支撐autowired。
一個例子
@Testpublic void testBeanFactoy() throws NamingException, SQLException, ParseException, IOException { //創建Bean工廠 DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); //給bean工廠添加bean定義,解析xml里面的bean放入bean工廠 loadBeanDefinitions(bf); //根據名字從bean工廠獲取bean Hello hello = (Hello) bf.getBean("hello"); hello.sayHello(); Hello2 hello2 = (Hello2) bf.getBean("hello2"); hello2.sayHello();}protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws IOException { XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); String[] configLocations = new String[] { "beans2.xml" }; if (configLocations != null) { beanDefinitionReader.loadBeanDefinitions(configLocations); }}
3.3 使用場景
-
不同條件下創建不同實例,用于統一管理bean
-
不同條件下調用不同工廠方法獲取不同場景下的bean
四、單例設計模式(Singleton Pattern)
4.1 介紹
單例模式是一種創建型模式,單例模式提供一個創建對象的接口,但是多次調用該接口返回的是同一個實例的引用,目的是為了保證只有一個實例,并且提供一個訪問這個實例的統一接口。
4.2 Spring中單例bean的創建
Spring中默認配置的bean的scope為singleton,也就是單例作用域。那么看看它是如何做到的。
在AbstractBeanFactory類里面的doGetBean方法:
protected Object doGetBean( final String name, final Class requiredType, final Object[] args, boolean typeCheckOnly) throws BeansException { final String beanName = transformedBeanName(name); Object bean = null; // 解決set循環依賴 Object sharedInstance = getSingleton(beanName); if (sharedInstance != null && args == null) { ... } else { ... // 創建單件bean. if (mbd.isSingleton()) { sharedInstance = getSingleton(beanName, new ObjectFactory() { public Object getObject() throws BeansException { try { return createBean(beanName, mbd, args); } catch (BeansException ex) { ... throw ex; } } }); bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); } //創建原型bean else if (mbd.isPrototype()) { ... } //創建request作用域bean else { ... } } ... return bean;}
getSingleton代碼:
public Object getSingleton(String beanName, ObjectFactory singletonFactory) { Assert.notNull(beanName, "'beanName' must not be null"); synchronized (this.singletonObjects) { Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { ... beforeSingletonCreation(beanName); ... try { singletonObject = singletonFactory.getObject(); } catch (BeanCreationException ex) { ... } finally { if (recordSuppressedExceptions) { this.suppressedExceptions = null; } afterSingletonCreation(beanName); } addSingleton(beanName, singletonObject); } return (singletonObject != NULL_OBJECT ? singletonObject : null); }}protected void addSingleton(String beanName, Object singletonObject) { synchronized (this.singletonObjects) { this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT)); this.singletonFactories.remove(beanName); this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); }}private final Map singletonObjects = CollectionFactory.createConcurrentMapIfPossible(16);
可知Spring內部是通過一個ConcurrentMap來管理單件bean的。獲取bean時候會先看看singletonObjects中是否有,有則直接返回,沒有則創建后放入。
看個時序圖:
Spring的bean工廠管理的單例模式管理的是多個bean實例的單例,是工廠模式管理所有的bean,而每個bean的創建又使用了單例模式。
4.4 使用場景
-
同一個jvm應用的不同模塊需要使用同一個對象實例進行信息共享。
-
需要同一個實例來生成全局統一的序列號。
五、原型設計模式(Prototype Pattern)
5.1 介紹
相比單例設計模式,原型模式是每次創建一個對象,下面看下spring是如何使用原型模式的。
5.2 Spring中原型bean的創建
創建原型bean需要在xml特別說明:
<bean id="hello" class="com.zlx.demo.Hello" scope="prototype"/>
protected <T> T doGetBean( final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly) throws BeansException { final String beanName = transformedBeanName(name); Object bean; // Eagerly check singleton cache for manually registered singletons. Object sharedInstance = getSingleton(beanName); if (sharedInstance != null && args == null) { ... } else { ... try { ... // Create bean instance. if (mbd.isSingleton()) { ... } //創建原型bean else if (mbd.isPrototype()) { // It's a prototype -> create a new instance. Object prototypeInstance = null; try { beforePrototypeCreation(beanName); prototypeInstance = createBean(beanName, mbd, args); } finally { afterPrototypeCreation(beanName); } bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd); } else { ... } } catch (BeansException ex) { cleanupAfterBeanCreationFailure(beanName); throw ex; } }... return (T) bean;}
createBean函數里面則是根據bean定義創建新bean,感興趣的可以看看。
5.3 使用場景
-
當有業務場景使用某個bean時候需要使用自己的一個拷貝的時候使用。
六、 策略模式(Strategy Pattern)
6.1 介紹
策略模式屬于行為性模式,它定義一系列的算法對象,使用時候可以使它們相互替換。
6.2 Spring中bean實例化策略
首先看下類圖:
從圖知道:接口InstantiationStrategy是實例化策略接口類,它定義了三個實例化接口,然后SimpleInstantiationStrategy實現了該策略,它主要做一些簡單的根據構造函數實例號bean的工作,然后CglibSubclassingInstantiationStrategy又繼承了SimpleInstantiationStrategy新增了方法注入方式根據cglib生成代理類實例化方法。
在AbstractAutowireCapableBeanFactory中管理了該策略的一個對象,默認是CglibSubclassingInstantiationStrategy策略,運行時候可以通過setInstantiationStrategy改變實例化策略,如果你自己寫個策略的話。
6.3 Spring中Aop代理策略
首先看AopProxyFactory接口類提供了createAopProxy接口,這個是策略模式的接口方法。然后DefaultAopProxyFactory實現了該接口作為策略的實現者。然后ProxyCreatorSupport里面引用了AopProxyFactory,并且提供了get,set方法用來運行時改變策略,這里Spring只實現了DefaultAopProxyFactory這一個策略,如果需要自己也可以寫個。
DefaultAopProxyFactory里面的createAopProxy的邏輯如下,可以在運行時根據參數決定用Cglib策略還是JDK動態代理策略生成代理類:
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { //如果XML打開了優化開關,或者設置為了代理目標類,或者目前類沒有接口 if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) { Class<?> targetClass = config.getTargetClass(); if (targetClass == null) { throw new AopConfigException("TargetSource cannot determine target class: " + "Either an interface or a target is required for proxy creation."); } //如果有接口,或者通過Proxy.newProxyInstance生成的,則使用jdk動態代理 if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) { return new JdkDynamicAopProxy(config); } //使用cglib return new ObjenesisCglibAopProxy(config); } else { //使用jdk動態代理 return new JdkDynamicAopProxy(config); } }
另外AopProxy也是一個策略接口類,具體實現的策略為JdkDynamicAopProxy,CglibAopProxy,ObjenesisCglibAopProxy。
6.4 Tomcat中Digester解析server.xml
tomcat中的Digester是為了解析server.xml的,其中每個元素都有一個解析規則就是Rule ,類圖如下:
DigestER一開始先指定不同的解析策略(Rule),然后在具體解析Server.xml時候根據節點不同使用不同解析策略來解析節點。
如圖在解析每個節點時候會先找到該節點對應的解析策略,然后循環去調用所有解析策略的方法去處理。
6.5 使用場景
運行時根據條件的不同使用不同的策略處理一個事情,與責任鏈不同在于,責任鏈是一個鏈條,一個事情可以被責任鏈里面所有節點處理,而策略模式則是只有有一個對象來處理。
七、 門面模式(Facade Pattern)
7.1 介紹
門面模式是一種結構性模式,它通過新增一個門面類對外暴露系統提供的一部分功能,或者屏蔽了內部系統的復雜性,對外部僅僅暴露一個簡單的接口,或者通過調用不同的服務對外提供統一的接口,讓使用者對這些內部服務透明化。
7.2 模板引擎Velocity中門面模式使用
Velocity里面的VelocityEngine和Velocity類都是RuntimeInstance類的門面,后者提供了模板渲染的所有功能,前兩者則是內部維護RuntimeInstance的實例,具體工作還是委托給RuntimeInstance來實現。
關于Veloctiy可以參考:https://www.atatech.org/articles/78435
如圖 RuntimeInstance提供了Velocity引擎的所用功能,VelocityEngine內部直接引用了RuntimeInstance的一個實例,VelocityEngine對外暴露的服務都是委托RuntimeInstance實現,并且每次new一個VelocityEngine內部都會有RuntimeInstance的一個實例被創建。而Velocity類調用了單例模式類RuntimeSingleton里面的方法,RuntimeSingleton又是RuntimeInstance的一個單例模式。
7.3 使用場景
-
當需要對外屏蔽一個系統的復雜性時候可以考慮使用門面模式對外提供簡單可讀性高的接口類。
-
當需要對外部暴露系統一部分權限的接口時候可以考慮使用門面模式減少系統權限。
-
當系統需要調用不同服務匯總后在對外提供服務時候可以考慮使用門面模式對外屏蔽細節,只暴露一個接口。
八、裝飾器模式(Decorator Pattern)
8.1 介紹
裝飾器模式是一種結構性模式,它的作用是對對象已有功能進行增強,但是不改變原有對象結構。這避免了通過繼承方式進行功能擴充導致的類體系臃腫。
8.2 Spring中BeanDefinitionDecorator
先看下類圖:
如圖ScopedProxyBeanDefinitionDecorator實現了decorate方法用來對scope作用域為request的bean定義進行包裝。
具體時序圖為:
class ScopedProxyBeanDefinitionDecorator implements BeanDefinitionDecorator { private static final String PROXY_TARGET_CLASS = "proxy-target-class"; @Override public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition, ParserContext parserContext) { boolean proxyTargetClass = true; if (node instanceof Element) { Element ele = (Element) node; if (ele.hasAttribute(PROXY_TARGET_CLASS)) { proxyTargetClass = Boolean.valueOf(ele.getAttribute(PROXY_TARGET_CLASS)); } } // 創建scoped的代理類,并注冊到容器 BeanDefinitionHolder holder = ScopedProxyUtils.createScopedProxy(definition, parserContext.getRegistry(), proxyTargetClass); String targetBeanName = ScopedProxyUtils.getTargetBeanName(definition.getBeanName()); parserContext.getReaderContext().fireComponentRegistered( new BeanComponentDefinition(definition.getBeanDefinition(), targetBeanName)); return holder; }}
關于ScopedProxyBeanDefinitionDecorator干啥用的呢:
<bean id="lavaPvgInfo" class="com.alibaba.lava.privilege.PrivilegeInfo" scope="request"> <property name="aesKey" value="666" /> <aop:scoped-proxy /> </bean>
其實就是處理<aop:scoped-proxy />
的,具體作用是包裝lavaPvgInfo的bean定義為ScopedProxyFactoryBean,作用是實現request作用域bean。
8.3 commons-collections包中ListUtils
如圖
ListUtils中的四個方法分別依賴list的四種裝飾器類對List功能進行擴充和限制。
其中FixedSizeList類通過禁止add/remove操作保證list的大小固定,但是可以修改元素內容。
其中UnmodifiableList類通過禁用add,clear,remove,set,保證list的內容不被修改。
其中SynchronizedList類通過使用Lock 來保證add,set,get,remove等的同步安全。
其中LazyList類則當調用get方法發現list里面不存在對象時候,自動使用factory創建對象。
8.4 使用場景
-
在不改變原有類結構基礎上,新增或者限制或者改造功能時候。
九、適配器模式(Adapter Pattern)
9.1 介紹
適配器模式屬于結構性模式,它為兩個不同接口之間互通提供了一種手段。
9.2 Spring中MethodInterceptor適配器
在Spring Aop框架中,MethodInterceptor接口被用來攔截指定的方法,對方法進行增強。
大家都知道在Aop中每個advistor 里面會有一個advice具體做切面動作,Spring提供了AspectJAfterReturningAdvice,AspectJMethodBeforeAdvice,AspectJAroundAdvice,AspectJAfterAdvice這幾個advice,在XML 配置aop時候會指定<aop:after-returning/>,<aop:before/>,<aop:around/>,<aop:after/>
,其實內部就是創建上面對應的這些advice。
從圖知道AspectJAfterReturningAdvice和AspectJMethodBeforeAdvice沒有實現MethodInterceptor接口,其他兩者則實現了該接口。而Spring Aop的方法攔截器卻必須是實現了MethodInterceptor的,所以Spring提供了對應的適配器來適配這個問題,分別是MethodBeforeAdviceAdapter和AfterReturningAdviceAdapter和ThrowsAdviceAdapter。
看下DefaultAdvisorAdapterRegistry的 getInterceptors方法:
public MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException { List<MethodInterceptor> interceptors = new ArrayList<MethodInterceptor>(3); //從advistor中獲取advice Advice advice = advisor.getAdvice(); //如果實現了MethodInterceptor則直接加入,比如AspectJAroundAdvice,AspectJAfterAdvice if (advice instanceof MethodInterceptor) { interceptors.add((MethodInterceptor) advice); } //否者看是否有當前advice的適配器,首先檢驗是否支持,支持則返回對應的適配器 for (AdvisorAdapter adapter : this.adapters) { if (adapter.supportsAdvice(advice)) { interceptors.add(adapter.getInterceptor(advisor)); } } if (interceptors.isEmpty()) { throw new UnknownAdviceTypeException(advisor.getAdvice()); } return interceptors.toArray(new MethodInterceptor[interceptors.size()]);}
以MethodBeforeAdviceAdapter為例子看下:
class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable { public boolean supportsAdvice(Advice advice) { return (advice instanceof MethodBeforeAdvice); } public MethodInterceptor getInterceptor(Advisor advisor) { MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice(); return new MethodBeforeAdviceInterceptor(advice); }}public class MethodBeforeAdviceInterceptor implements MethodInterceptor, Serializable { private MethodBeforeAdvice advice; /** * Create a new MethodBeforeAdviceInterceptor for the given advice. * @param advice the MethodBeforeAdvice to wrap */ public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) { Assert.notNull(advice, "Advice must not be null"); this.advice = advice; } public Object invoke(MethodInvocation mi) throws Throwable { this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis() ); return mi.proceed(); }}
可知MethodBeforeAdviceInterceptor繼承了MethodInterceptor作為了一個適配器內部委托請求給MethodBeforeAdvice。
9.3 使用場景
-
兩個系統交互時候由于接口參數不一樣沒辦法直接對接,則可以搞個適配器接口做參數轉換。
-
適配器模式經常是在一個系統或者設計已經定型時候用的,而不是在初始設計時候。一般是因為不影響現在業務情況下,通過適配方式統一接口
十、模板設計模式(Template Pattern)
10.1 前言
模板設計模式是一種行為設計模式,它使用一個抽象類定義了一個模板,這個模板里面定義了一系列的接口,子類則只需要繼承該抽象類并且根據需要重寫一部分接口。
10.2 ibatis2中AbstractDAOTemplate
如圖AbstractDAOTemplate是抽象模板類,里面定義了configure方法,configure方法里面定義了好多protected方法,其中就有些是抽象方法。類SpringDAOTemplate,IbatisDAOTemplate,GenericCIDAOTemplate,GenericSIDAOTemplate則繼承了AbstractDAOTemplate類并重寫了一部分方法。
10.3 Tomcat中Digester里面的Rule
tomcat中的Digester是為了解析server.xml的,其中每個元素都有一個解析規則就是Rule ,類圖如下:
如圖:Rule是抽象類,對于每個解析的節點來說Rule提供了解析所需所有的方法,而他的子類則根據自己的特殊性重寫一部分方法來支持自己的特性。
10.4 Tomcat中Endpoint
如圖AbstractEndpoint是個抽象類,定義了Endpoint的所有接口,然后JIoEndpoint繼承了該類并且重寫了一部分重要的方法實現了BIO方式endpoint,NioEndpoint則重寫了方法實現了NIO的endpoint。
10.5使用場景
-
當多個子類具有共同的操作流程邏輯,并且其中某些流程節點操作需要自己定制化時候。
十一、 建造者模式(Builder Pattern)
11.1 前言
建造者模式是一種創建型模式,將一個復制對象的創建屏蔽到接口內部,用戶使用時候只需要傳遞固定的參數,內部就會執行復雜邏輯后返回給用戶需要的對象,用戶不需要知道創建的細節。
11.2 Mybatis中的SqlSessionFactoryBuilder
如圖mybaits中的SqlSessionFactoryBuilder就是典型的創建者模式,他內部有多個build方法,根據參數的不同創建出SqlSessionFactory對象,使用者只需要傳遞具體參數而不用關心內部是如何創建出需要的對象的。SqlSessionFactoryBean大家應該很熟悉,在xml里面配置的。
11.3 使用場景
-
當一個對象比較復雜并且容易出錯時候,可以考慮這種模式去屏蔽創造細節。
十二、 觀察者模式(Observer Pattern)
12.1 前言
觀察者模式是一種行為模式,它定義了當一個對象的狀態或者屬性發生變化時候,通知其他對這些狀態感興趣的對象。觀察者模式也叫發布-訂閱模式,就是說當你訂閱了摸一個主體時候,如果發布者改變了主題內容,那么所有訂閱這個主體者都會受到通知。
12.2 Spring中ApplicationListener
如圖 黃色部分的listener們可以認為是訂閱者,紅色的context是發布者,context在IOC不同狀態會給這些訂閱者發布不同的消息通知訂閱者容器狀態。藍色的為具體的事件(這里為容器不同狀態),其中ContextRefreshedEvent是IOC刷新完成(也就是bean解析完成,創建完畢并且autowired完成)后的事件這個經常用。
這里context并不是直接來管理黃色的listener訂閱者的,而是委托給了綠色的部分,該部分是可以增加刪除訂閱者,并且發布事件給訂閱者。
其實Tomact中的Lifecycle也是這種機制,這里不再贅述。
12.3 使用場景
-
滿足發布-訂閱條件的,當一個對象狀態或者屬性變化,需要把這種變化通知到訂閱者時候。
十三、命令模式(Command Pattern)
13.1 介紹
命令模式是一種行為模式,通過把命令封裝為一個對象,命令發送者把命令對象發出后,就不去管是誰來接受處理這個命令,命令接受者接受到命令對象后進行處理,也不用管命令是誰發出的,所以命令模式實現了發送者與接受者之間的解耦,而具體把命令發送給誰還需要一個控制器。
13.2 Tomcat中命令模式
tomcat作為一個服務器本身會接受外部大量請求,當一個請求過來后tomcat根據域名去找對應的host,找到host后會根據應用名去找具體的context(應用),然后具體應用處理請求。對于具體host來說他不關心這個請求是誰給的,對應請求來說他不必關心誰來處理,但是兩者是通過request封裝請求對象進行關聯起來。
tomcat中Connector作為命令發出者,Connector接受到請求后把請求內容封裝為request對象(命令對象),然后使用CoyoteAdapter作為分發器把請求具體發配到具體的host,host在根據request對象找到具體的context,至此找到了具體的應用,交給具體應用處理。
另外對于使用springmvc的應用來說,上面找到具體應用,但是具體交給那個controller來處理那,這是不是也是命令模式的使用那。
13.3 使用場景
-
當事件發送者和接受者直接需要完全解耦(直接并不存在引用關系)時候。
十四、總結
設計模式中每一個模式都描述了在我們工作中不斷重復發生的問題,以及問題的解決方案,所以真正掌握設計模式可以避免我們做不必要的重復勞動。