技能篇:實際開發(fā)常用設計模式
創(chuàng)建型
單例模式
單例對象能節(jié)約系統(tǒng)資源,一個對象的創(chuàng)建和消亡的開銷可能很小。但是日常的服務接口,就算是一般小公司也有十幾萬的QPS吧。每一次的功能運轉都創(chuàng)建新的對象來響應請求,十幾萬對象的創(chuàng)建和銷毀,想想就是一筆大開銷,所以 spring 管理構造的 bean 對象一般都是單例。而且單例模式可以更好的解決并發(fā)的問題,方便實現(xiàn)數(shù)據(jù)的同步性
優(yōu)點
- 在內存中只有一個對象,節(jié)省內存空間
- 避免頻繁的創(chuàng)建銷毀對象,可以提高性能
- 避免對共享資源的多重占用,簡化訪問
- 為整個系統(tǒng)提供一個全局訪問點
缺點
- 不適用于變化頻繁的對象
- //餓漢式
- private static Singleton singleton = new Singleton();
- //懶漢式
- private static Singleton singleton;
- public static Singleton getSingleton(){
- if (singleton == null) {
- singleton = new Singleton(); //被動創(chuàng)建,在真正需要使用時才去創(chuàng)建
- }
- return singleton;
- }
- //雙重判斷加鎖機制
- private volatile static Singleton instance;
- //程序運行時創(chuàng)建一個靜態(tài)只讀的進程輔助對象
- public static Singleton GetInstance() {
- //先判斷是否存在,不存在再加鎖處理
- if (instance == null){
- synchronized (Singleton.class){
- if(instance == null){
- instance = new Singleton();
- }
- }
- }
- return instance;
- }
- //靜態(tài)初始化
- private static readonly Singleton instance= new Singleton();
- public static Singleton GetInstance(){
- return instance;
- }
工廠模式
使用者不關心對象的實例化過程,只關心對象的獲取。工廠模式使得產(chǎn)品的實例化過程和消費者解耦
優(yōu)點
- 一個調用者想創(chuàng)建一個對象,只需通過其名稱或其他唯一鍵值在工廠獲取
- 擴展性高,如果想增加生產(chǎn)一種類型對象,只要擴展工廠類就可以
缺點
- 工廠類不太理想,因為每增加一產(chǎn)品,都要在工廠類中增加相應的生產(chǎn)判斷邏輯,這是違背開閉原則的
- public interface Sender{ public void send(); }
- public class MailSender implements Sender {
- @Override
- public void send() {
- System.out.println("this is mailsender!");
- }
- }
- public class SmsSender implements Sender {
- @Override
- public void send() {
- System.out.println("this is sms sender!");
- }
- }
- public class SendFactory {
- public Sender produce(String type) {
- if ("mail".equals(type)) {
- return new MailSender();
- } else if ("sms".equals(type)) {
- return new SmsSender();
- } else {
- return null;
- }
- }
- //若還有其他產(chǎn)品 則在工廠里加對應的 produce 方法
- }
建造者模式
主要解決在軟件系統(tǒng)中一個復雜對象的創(chuàng)建工作,其通常由各個部分的子對象用一定的算法構成;由于需求的變化,這個復雜對象的各個部分經(jīng)常面臨著劇烈的變化,但是將它們組合在一起的算法卻相對穩(wěn)定
優(yōu)點
- 擴展性好,對象每一個屬性的構建相互獨立,有利于解耦。
- 建造者可以對創(chuàng)建過程逐步細化,而不對其它模塊產(chǎn)生任何影響,便于控制細節(jié)風險
缺點
- 如果對象建造者發(fā)生變化,則建造者也要同步修改,后期維護成本較大
- 一種建造者對應一種類型建造,一個建造者基本很難建造多種類型對象
- @Data
- class Product {
- private String name;
- private String price;
- // Product 的建造者 Builder
- public static class Builder{
- public static Builder builder(){
- Builder builder = Builder();
- }
- private Product product = new Product();
- public Builder name(String name){ product.name = name; return this;}
- public Builder price(String price){ product.price = price; return this; }
- //返回產(chǎn)品對象
- public Product build() { return product; }
- }
- }
結構型
適配器模式
連通上下游功能。一般是現(xiàn)有的功能和產(chǎn)品要求的接口不兼容,需要做轉換適配。平時見到的 PO,BO,VO,DTO 模型對象之間的相互轉換也是一種適配的過程
- 優(yōu)點:提高了類的復用,靈活性好
- 缺點:過多地使用適配器,會讓系統(tǒng)非常零亂,不易整體進行把握。比如,明明看到調用的是 A 接口,其實內部被適配成了 B 接口的實現(xiàn)
- //類的適配器模式
- public class Source {
- public void sayHello() {
- System.out.println("lwl:hello!");
- }
- }
- public interface Targetable {
- /* Source方法相同 */
- public void sayHello();
- /* 新增的方法 */
- public void hi();
- }
- // Source 用 Adapter 適配成 Targetable
- public class Adapter extends Source implements Targetable {
- @Override
- public void hi() {
- System.out.println("csc:hi!");
- }
- }
- //對象的適配器模式
- public class Source {
- public void sayHello() {
- System.out.println("lwl:hello!");
- }
- }
- public interface Targetable {
- /* Source方法相同 */
- public void sayHello();
- /* 新增的方法 */
- public void hi();
- }
- // Source的對象適配成 Targetable
- public class Adapter implements Targetable {
- private Source source;
- public Adapter(Source source){ this.source = source; }
- public void sayHello(){ source.sayHello(); }
- @Override
- public void hi() {
- System.out.println("csc:hi!");
- }
- }
裝飾器模式
增強對象功能,動態(tài)的為一個對象增加功能,而且還能動態(tài)撤銷。(繼承不能做到這一點,繼承的功能是靜態(tài)的,不能動態(tài)增刪)
- public interface Show(){ public void acting(); }
- public class Artist implements Show {
- public void acting(){
- System.out.println("lwl 在唱歌!");
- }
- }
- public class DecoratorArtist implements Show{
- Artist artist;
- DecoratorArt(Artist artist){
- this.artist = artist;
- }
- public void acting(){
- System.out.println("lwl 在彈鋼琴!"); //增強的功能
- this.artist.acting();
- System.out.println("表演完畢!"); //增強的功能
- }
- }
代理模式
代理類是客戶類和委托類的中介,可以通過給代理類增加額外的功能來擴展委托類的功能,這樣只需要修改代理類而不需要再修改委托類,符合代碼設計的開閉原則
- 和裝飾器模式的區(qū)別:代理模式著重于增強類功能,且對面屏蔽原對象的創(chuàng)建過程;裝飾器模式增強的是對象,且裝飾器模式有一個動態(tài)傳遞原對象的步驟
- 和對象的適配器模式優(yōu)點像:不過代理模式著重的是對原功能增強,適配器模式著重的是對新功能的兼容
- 優(yōu)點-1、職責清晰。2、高擴展性
- public class Artist implements Show {
- public void acting(){
- System.out.println("lwl 在唱歌!");
- }
- }
- public class ProxyArtist implements Show{
- Artist artist;
- ProxyArtist(){
- this.artist = new Artist();//屏蔽了 artist 對象的創(chuàng)建
- }
- public void acting(){
- System.out.println("lwl 在彈鋼琴!"); //增強的功能
- this.artist.acting();
- System.out.println("表演完畢!"); //增強的功能
- }
- }
- public class Demo {
- public static void main(String[] arg){
- Show show = new ProxyArtist();
- show.acting();
- }
- }
橋接模式
橋接模式側重于功能的抽象,從而基于這些抽象接口構建上層功能。一般的java 項目都會將接口和實現(xiàn)分離原因,就是基于橋接模式。提高了系統(tǒng)的擴展能力,當引用的底層邏輯有不同的設計實現(xiàn)時,繼承抽象接口重新實現(xiàn)一套即可,舊的不變,符合代碼設計的開閉原則
- jdbc 的驅動:常用的JDBC 和 DriverManager,JDBC進行連接數(shù)據(jù)庫的時候,在各個數(shù)據(jù)庫之間進行切換,基本不需要動太多的代碼,原因就是JDBC提供統(tǒng)一接口,每個數(shù)據(jù)庫提供各自的實現(xiàn),用一個叫做數(shù)據(jù)庫驅動的程序來橋接
- Unix 的文件系統(tǒng):VFS(virtual File System)使得 Unix 系統(tǒng)可以在不同物理介質上的不同文件系統(tǒng)進行讀寫
- public interface FileSystem(){
- public void open(int file);
- public String loading(int file);
- public void store(int file, String data);
- }
- //網(wǎng)絡上的文件系統(tǒng)
- public class NetFileSystem implements FileSystem {
- public void open(int file){ System.out.println(" netfile opening...."); }
- public String loading(int file) {System.out.println(" net loading ...."); }
- public void store(int file, String data) {System.out.println(" send to network ...."); }
- }
- //磁盤文件系統(tǒng)
- public class DiskFileSystem implements FileSystem{
- public void open(int file){ System.out.println(" disk opening...."); }
- public String loading(int file) {System.out.println(" disk loading ...."); }
- public void store(int file, String data) {System.out.println(" write back disk ...."); }
- }
- public class Linux {
- FileSystem fileSystem;
- //底層功能提供接口,橋接模式:功能和具體實現(xiàn)分離
- //可以橋接 NetFileSystem 或者 DiskFileSystem 作為文件系統(tǒng)
- public void set(FileSystem fileSystem){ this.fileSystem = fileSystem; }
- //上層功能讀數(shù)據(jù)
- public String read(int file){
- fileSystem.open(file);
- ... // Linux 自己的系統(tǒng)功能
- fileSystem.loading(file);
- ...
- }
- //上層功能寫數(shù)據(jù)
- public String write(int file, String data){
- fileSystem.open(file);
- ....
- fileSystem.store(file,data);
- }
- }
可配合適配器模式使用
享元模式
多個對象共享某些屬性。在創(chuàng)建有大量對象時,可能會造成內存溢出,把其中共同的部分抽象出來,如果有相同的請求,直接返回在內存中同一份屬性,避免重新創(chuàng)建
- 如 jdbc 連接池的連接對象,它們會共享池對象的 url、driverClassName、username、password 等屬性
- public class ConnectionPool {
- private Vector<Connection> pool;
- /*公有屬性*/
- private String url = "jdbc:mysql://localhost:3306/test";
- private String username = "root";
- private String password = "root";
- private String driverClassName = "com.mysql.jdbc.Driver";
- public ConnectionPool() {
- pool = new Vector<Connection>(poolSize);
- for (int i = 0; i < poolSize; i++) {
- Class.forName(driverClassName);
- // 每一個 conn 共享了 driverClassName ,url, username, password 等屬性
- Connection conn = DriverManager.getConnection(url, username, password);
- pool.add(conn);
- }
- }
- ....
- }
外觀模式
- 用多個不同的對象實現(xiàn)一組更復雜的功能。使得類與類之間的關系解耦。如 spring 將使用各個簡單的 component、dao 實現(xiàn)復雜的service,就是一種外觀模式
- 功能的組合,組合優(yōu)于繼承
- public class DAO {
- public void queryData(){
- System.out.print(" query data ")
- }
- }
- public class Deal {
- public void dealData(){
- System.out.print(" dealing data ")
- }
- }
- public class Sender {
- public void send(){
- System.out.print(" send data ")
- }
- }
- public class Service(){
- private DAO dao;
- private Deal deal;
- private Sender sender;
- //封裝 DAO,Deal,Sender 的功能,統(tǒng)一對外提供服務
- public void reponse(){
- dao.queryData();
- deal.dealData();
- sender.send();
- }
- }
行為型
策略模式
策略模式側重于不同的場景使用不同的策略。在有多種算法相似的情況下,解決 if...else 所帶來的復雜和難以維護
和橋接模式的區(qū)別:而橋接模式是結構型模式,側重于分離底層功能的抽象和實現(xiàn),底層只有一種實現(xiàn)也可以
- // 上學的策略
- abstract class Strategy{
- private static final Map<Integer,Strategy> strategyMap = new ConcurrentHashMap<>();
- public Strategy(){
- strategyMap.put(getType(), this);
- }
- public static Strategy routing(int type){
- return strategyMap.get(type);
- }
- abstract int getType();
- abstract void method(); //留待子類實現(xiàn)差異
- }
- //跑路去學校
- class RunningStrategy extends Strategy{
- int getType() { return 0; }
- void method() { System.out.println(" Run to school "); }
- }
- //公交去學校
- class BusStrategy extends Strategy{
- int getType() { return 1; }
- void method() { System.out.println(" Go to school by bus "); }
- }
- //飛去學校
- class FlyStrategy extends Strategy{
- int getType() { return 2; }
- void method() { System.out.println(" Fly to school "); }
- }
- class Context{
- //使用不同的策略
- void method(int strategy){
- Strategy.routing(strategy).method();
- }
- }
模板方法
和享元模式有一定的相似處,享元模式側重于屬性的共享,而且是結構上的引用,不一定需要繼承;而模板方法是共享相同行為,一定有繼承行為
區(qū)別于策略模式是它有能抽象出來的共同行為,每一個子類再實現(xiàn)有差異細節(jié)
- abstract class AbstractHandler{
- // handle是抽象出來的共同邏輯
- void handle(String data){
- System.out.println("通用邏輯1...");
- stepOne(data);
- System.out.println("通用邏輯2...");
- stepTwo(data);
- System.out.println("通用邏輯3...");
- }
- abstract void stepOne(String data); //留待子類實現(xiàn)差異
- abstract void stepTwo(String data); //留待子類實現(xiàn)差異
- }
- class HelloHandler extends AbstractHandler{
- @Override
- void stepOne(String data) {
- System.out.println("hello: "+data);
- }
- @Override
- void stepTwo(String data) {
- System.out.println("hi: "+data);
- }
- }
迭代子模式
循環(huán)處理多個相同對象,用來遍歷集合或者數(shù)組
- //迭代的抽象接口
- public interface Iterator {
- //前移
- public Object previous();
- //后移
- public Object next();
- public boolean hasNext();
- }
- // 數(shù)組的迭代類
- public class ArrayIterator implements Iterator {
- private Object[] datas;
- private int cur = 0;
- public ArrayIterator(Object[] datas){
- this.datas = datas;
- }
- public String previous() {
- if(cur > 0){ cur--;}
- return datas[cur];
- }
- public Object next() {
- if(cur < datas.length-1){ cur++;}
- return datas[cur];
- }
- public boolean hasNext() {
- return pos < datas.length-1 ? true :false;
- }
- }
責任鏈模式
負責處理上游的傳遞下來的對象,并傳遞給下一個處理者
和迭代子模式的區(qū)別,責任鏈模式是多個hander處理同一個data,且 hander 處理具有順序性,不用全部 hander 處理,可在某一 hander 中斷,也可繼續(xù)傳遞。
- abstract class Handler<T,R> {
- private Handler<R,?> next;
- abstract R handle(T data);
- public void setNext(Handler<R, ?> next){ this.next = next; }
- public void loopHandle(T data){
- R result = this.handle(data);
- if(next!=null && result!=null ) { next.loopHandle(result); }
- }
- }
- //負責問候
- class HelloHandler extends Handler<String, Integer> {
- Integer handle(String data) {
- System.out.println(data + " hello! ");
- return 10;
- }
- }
- //負責計數(shù)
- class CountHandler extends Handler<Integer, Double> {
- Double handle(Integer data) {
- System.out.println(" it is " + data);
- return 2.0;
- }
- }
- public class demo{
- public static void main(String[] args){
- HelloHandler hello = new HelloHandler();
- CountHandler count = new CountHandler();
- hello.setNext(count);
- hello.loopHandle("lwl");
- }
- }
觀察者模式
事件通知: 定義對象間的一種一對多的依賴關系,當一個對象的狀態(tài)發(fā)生改變時,所有依賴于它的對象都得到通知。
優(yōu)點:觀察者和被觀察者是抽象耦合的
缺點
- 如果一個被觀察者對象有很多的直接和間接的觀察者的話,將所有的觀察者都通知到會花費很多時間
- 如果在觀察者和觀察目標之間有循環(huán)依賴的話,觀察目標會觸發(fā)它們之間進行循環(huán)調用,可能導致系統(tǒng)崩潰
- //觀察者
- public abstract class Observer<T> {
- public abstract void update(T data);
- }
- // 被觀察對象
- public class Subject<T> {
- private List<Observer<T>> observers = new ArrayList<>();
- private T state;
- public void deal() {
- ....// 邏輯處理
- //如果修改了 state,通知觀察者
- if(...) notifyAllObservers();
- }
- //增加一個觀察觀察
- public void observe(Observer<T> observer) {
- observers.add(observer);
- }
- public void notifyAllObservers() {
- for (Observer<T> observer : observers) {
- observer.update(state);
- }
- }
- }
狀態(tài)機模式
不同的狀態(tài)不同的響應,實現(xiàn)狀態(tài)之間的轉移
和策略模式的區(qū)別
- 狀態(tài)機模式是策略模式的孿生兄弟。策略模式可以讓用戶指定更換的策略算法,而狀態(tài)機模式是狀態(tài)在滿足一定條件下的自動更換,用戶無法指定狀態(tài),最多只能設置初始狀態(tài)
- 狀態(tài)機模式重點在各狀態(tài)之間的切換,從而做不同的事情;而策略模式更側重于根據(jù)具體情況選擇策略,并不涉及切換
- interface State<T> {
- //當前狀態(tài)進行處理數(shù)據(jù),并返回下一個狀態(tài)
- abstract State<T> action(T data);
- }
- @Data
- class Context<T>{
- private State<T> state;
- public void invoke(T data){
- state != null ? state = state.action(data) : System.out.println(" nothing " + data);
- }
- }
- // HelloState -> HiState
- class HelloState implements State<String>{
- public State<String> action(String data) {
- System.out.println("hello!" + data);
- return new HiState();
- }
- }
- // HiState -> FineState
- class HiState implements State<String>{
- public State<String> action(String data) {
- System.out.println("how are you ?" + data);
- return new FineState();
- }
- }
- //最后的狀態(tài)
- class FineState implements State<String>{
- public State<String> action(String data) {
- System.out.println("I am fine!");
- return null;
- }
- }
- public class demo{
- public static void main(String[] args){
- Context<String> context = new Context<>();
- context.setState(new HelloState());
- context.invoke("lwl");
- context.invoke("lwl");
- context.invoke("lwl");
- context.invoke("lwl");
- }
- }
備忘錄
記錄上一次的狀態(tài),方便回滾。很多時候我們是需要記錄當前的狀態(tài),這樣做的目的就是為了允許用戶取消不確定或者錯誤的操作,恢復到原先的狀態(tài)
缺點:消耗資源。如果類的成員變量過多,勢必會占用比較大的資源,而且每一次保存都會消耗一定的內存
- @Data
- public class Memento {
- private String state;
- public Memento(String state){ this.state = state; }
- }
- @Data
- public class Storage {
- private String value;
- public void storeMemento(){
- return new Memento(value);
- }
- public void restoreMemento(Memento memento){
- this.value = memento.getValue();
- }
- public void action(){ System.out.println(" Storage類邏輯運行 ");}
- }
- public class MementoPatternDemo {
- public static void main(String[] args) {
- Storage storage = new Storage();
- storage.setValue(1);
- storage.storeMemento();//備忘,一下
- storage.action();//....邏輯運行
- restoreMemento(Memento memento);//使用備忘錄的恢復狀態(tài)
- }
- }