Java設計模式
設計模式:是一套被反復使用、多數人知曉的、經過分類編目的、代碼設計經驗的總結,
面向物件設計原則
都是為了高內聚低耦合原則,編程時基本都要遵守
單一職責原則
分類原則:一種人只干一種事,
舉例:(比較簡單就不代碼了)
人可以干的事情有很多:敲代碼、唱歌、跳舞、打籃球....以人設定成一個類,里面的方法就太多太雜了,所以可以有多個類:程式員(敲代碼)、音樂人(唱歌)、愛豆(跳舞)、NBA球員(打籃球),這樣類就具體化了,可以干的事情也就具體了,一旦需要用哪個方法就知道從哪個類里呼叫了,
開閉原則
開:提供方提供
抽象類/介面/方法 等,實作類可以決定行為,閉:呼叫方呼叫時,盡量不需要修改代碼,
定義:一個軟體物體,比如類、模塊和函式應該對擴展開放,對修改關閉,其中,對擴展開放是針對提供方來說的,對修改關閉是針對呼叫方來說的,
舉例:
//介面
public interface AccountService {
//實作注冊賬戶
void createAccount(String username,String password,String email);
}
//實作類
public class AccountServiceImpl implements AccountService {
public void createAccount(String username,String password,String email) {
....
}
}
里氏替換原則
對子類的特別定義:父類方法非抽象方法,子類不可以多載(覆寫),但如果父類有抽象方法則子類必須實作父類的抽象方法,子類也可以撰寫自己的方法
里氏替換原則(Liskov Substitution Principle)是對子型別的特別定義,所有參考基類的地方必須能透明地使用其子類的物件,
白話:子類可以擴展父類的功能,但不能改變父類原有的功能,有以下四原則:(重點在一二)
- 子類可以實作父類的抽象方法,但不能覆寫父類的非抽象方法,
- 子類可以增加自己特有的方法,
- 當子類的方法多載父類的方法時,方法的前置條件(即方法的輸入/入參)要比父類方法的輸入引數更寬松,
- 當子類的方法實作父類的方法時(重寫/多載或實作抽象方法),方法的后置條件(即方法的輸出/回傳值)要比父類更嚴格或與父類一樣,
如:子類繼承了父類,但沒有修改父類的非抽象方法
public abstract class Coder {
public abstract void eat(); //這個行為還是定義出來,但是不實作
public void coding() {
System.out.println("我會打代碼");
}
class JavaCoder extends Coder{
public void game(){ //子類自己的額外的方法
System.out.println("艾歐尼亞最強王者已上號");
}
public void eat(){ //子類實作父類的抽象方法(必須)
System.out.println("干啥啥不行,干飯第一名!")
}
}
}
依賴倒轉原則
使用Spring注解 注入介面,這樣需求更改后實作類可以自由撰寫,不會影響到controller層(將每一層都分隔開來降低耦合性)
定義:高層模塊不應依賴于底層模塊,它們都應該依賴抽象,抽象不應依賴于細節,細節應該依賴于抽象,
傳統:沒有介面而是類與類之間的物件創建,一旦需求變化,類就需要重寫,這樣其他類也需要修改
public class Main {
public static void main(String[] args) {
UserController controller = new UserController();
}
static class UserMapper {
//CRUD...
}
static class UserServiceNew { //由于UserServiceNew發生變化,會直接影響到其他高層模塊
UserMapper mapper = new UserMapper();
//業務代碼....
}
static class UserController { //焯,干嘛改底層啊,我這又得重寫了
UserService service = new UserService(); //哦豁,原來的不能用了
UserServiceNew serviceNew = new UserServiceNew(); //只能修改成新的了
//業務代碼....
}
}
Spring框架:使用注解注入介面bean,這樣實作類可隨便改,只要最后的實作類實作了該介面即可
//controller
public class LoginApiController {
@Autowired //Spring注解注入介面bean
private VerifyService verifyService;
@GetMapping("/verify-code")
public RestBean<Void> verifyCode(@RequestParam("email") String email) {
try {
verifyService.sendVerifyCode(email);
return new RestBean<>(200, "郵箱發送成功!");
} catch (Exception e) {
return new RestBean<>(500, "郵箱發送失敗!");
}
}
}
介面隔離原則
對介面進行細分,避免介面中定義的方法,在實作類中用不上,
舉例:定義一個介面,有方法:設備芯片、設備名稱、設備記憶體,這樣的介面只有電腦、手機等實作類才可以實作,而對于風扇、臺燈等普通設備實作類而言確只有設備名稱才是有效的方法,于是就需要把介面進行細化成兩個介面,
interface SmartDevice { //智能設備才有getCpu和getMemory
String getCpu();
String getType();
String getMemory();
}
interface NormalDevice { //普通設備只有getType
String getType();
}
//電腦就是一種電子設備,那么我們就繼承此介面
class Computer implements SmartDevice {
@Override
public String getCpu() {
return "i9-12900K";
}
@Override
public String getType() {
return "電腦";
}
@Override
public String getMemory() {
return "32G DDR5";
}
}
//電風扇也算是一種電子設備
class Fan implements NormalDevice {
@Override
public String getType() {
return "風扇";
}
}
合成復用原則
優先使用物件組合,而不是通過繼承來達到復用的目的,
合成復用原則(Composite Reuse Principle)的核心就是委派,
情況:如果A類里寫了想要的方法,為了不在B類不重復撰寫代碼,可以在B類中設定一個方法:將A類的物件作為引數并在設定的方法里通過物件獲取到A類中想要的方法,【此時不建議使用繼承,因為容易引起安全隱患,如:A中有一下資訊(密碼欄位)不方便傳遞】
舉例:
class A {
public void connectDatabase(){
System.out.println("我是連接資料庫操作!");
}
}
class B {
A a;
public B(A a){ //在構造時就指定好
this.a = a;
}
public void test(){
System.out.println("我是B的方法,我也需要連接資料庫!");
a.connectDatabase(); //也是通過物件A去執行
}
}
迪米特法則
每一個軟體單位對其他單位都只有最少的知識,而且局限于那些與本單位密切相關的軟體單位,
迪米特法則(Law of Demeter)又稱最少知識原則,是對程式內部資料互動的限制,
簡單來說就是,一個類/模塊對其他的類/模塊有越少的互動越好,當一個類發生改動,那么,與其相關的類(比如用到此類啥方法的類)需要盡可能少的受影響(比如修改了方法名、欄位名等,可能其他用到這些方法或是欄位的類也需要跟著修改)這樣我們在維護專案的時候會更加輕松一些,
白話:在設計方法引數的時候,保證不多給方法多余的引數,例如:方法只需要一個用戶的ip地址就可以執行,方法引數就不要寫成需要輸入用戶物件,然后在方法里面通過物件再去呼叫其ip出來;而是在呼叫方法前就把用戶物件的ip取出來,然后作為引數來呼叫方法,
舉例:
正面教材
public class Main {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("localhost", 8080);
Test test = new Test();
test.test(socket.getLocalAddress().getHostAddress()); //在外面決議好就行了
}
static class Test {
public void test(String str){ //一個字串就能搞定,就沒必要丟整個物件進來
System.out.println("IP地址:"+str);
}
}
}
反面教材
public class Main {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("localhost", 8080); //假設我們當前的程式需要進行網路通信
Test test = new Test();
test.test(socket); //現在需要執行test方法來做一些事情
}
static class Test {
/**
* 比如test方法需要得到我們當前Socket連接的本地地址
*/
public void test(Socket socket){
System.out.println("IP地址:"+socket.getLocalAddress());
}
}
}
創建型設計模式
針對
物件/類創建時的優化
工廠方法模式(了解)
通過定義頂層抽象工廠類,通過繼承的方式,針對于每一個產品都提供一個工廠類用于創建,
情況:只適用于簡單物件,當我們需要生產許多個產品族的時候,這種模式就有點乏力了
創建物件不再使用傳統的new,而是創建一個工廠類,作為all物體類創建物件的一個封裝類,(避免了更改類名、構造方法時,需要修改大量的代碼)
簡單工廠模式:(不靈活,不建議)
優點:簡單明了
缺點:不符合開閉原則,如果輸入沒有提前寫好的水果則就需要再添加每個類里的代碼
//水果抽象類
public abstract class Fruit {
private final String name;
public Fruit(String name){
this.name = name;
}
@Override
public String toString() {
return name+"@"+hashCode(); //列印一下當前水果名稱,還有物件的hashCode
}
}
//水果物體類
public class Apple extends Fruit{ //蘋果,繼承自水果
public Apple() {
super("蘋果");
}
}
public class Orange extends Fruit{ //橘子,也是繼承自水果
public Orange() {
super("橘子");
}
}
//水果工廠
public class FruitFactory {
/**
* 這里就直接來一個靜態方法根據指定型別進行創建
* @param type 水果型別
* @return 對應的水果物件
*/
public static Fruit getFruit(String type) {
switch (type) {
case "蘋果":
return new Apple();
case "橘子":
return new Orange();
default:
return null;
}
}
}
//主方法
public class Main {
public static void main(String[] args) {
Fruit fruit = FruitFactory.getFruit("橘子"); //直接問工廠要,而不是我們自己去創建
System.out.println(fruit);
}
}
工廠方法模式:通過范型靈活實作
優點:如果新增了水果型別,直接創建一個新的工廠類就行,不需要修改之前已經撰寫好的內容,
缺點:一種水果就有一種新的工廠類,太多工廠類了
//水果抽象類
public abstract class Fruit {
private final String name;
public Fruit(String name){
this.name = name;
}
@Override
public String toString() {
return name+"@"+hashCode(); //列印一下當前水果名稱,還有物件的hashCode
}
}
//水果工廠
public abstract class FruitFactory<T extends Fruit> { //將水果工廠抽象為抽象類,添加泛型T由子類指定水果型別
public abstract T getFruit(); //不同的水果工廠,通過此方法生產不同的水果
}
//Apple工廠
public class AppleFactory extends FruitFactory<Apple> { //蘋果工廠,直接回傳Apple,一步到位
@Override
public Apple getFruit() {
return new Apple();
}
}
//主方法
public class Main {
public static void main(String[] args) {
test(new AppleFactory()::getFruit); //比如我們現在要吃一個蘋果,那么就直接通過蘋果工廠來獲取蘋果
}
//此方法模擬吃掉一個水果
private static void test(Supplier<Fruit> supplier){
System.out.println(supplier.get()+" 被吃掉了,真好吃,");
}
}
抽象工廠模式
情況:適用于有一系列產品的公司,
優點:只跟介面打交道,不用去管具體的實作類,只用知道幾個產品等級 幾個產品族即可
缺點:容易違背開閉原則,一旦增加了一種產品,此時就必須去修改抽象工廠的介面,這就涉及到抽象工廠類的以及所有子類的改變
舉例:

一個品牌的產品叫產品族,一個產品種類叫產品等級
實際上這些產品都是成族出現的,比如小米的產品線上有小米12,小米平板等,華為的產品線上也有華為手機、華為平板,但是如果按照我們之前工廠方法模式來進行設計,那就需要單獨設計9個工廠來生產上面這些產品,顯然這樣就比較浪費時間的,
我們就可以使用抽象工廠模式,我們可以將多個產品,都放在一個工廠中進行生成,按不同的產品族進行劃分,比如小米,那么我就可以安排一個小米工廠,而這個工廠里面就可以生產整條產品線上的內容,包括小米手機、小米平板、小米路由等,
類圖:

代碼實作:
/*以華為和小米分別可以生產自家的手機和路由器為例 【以下介面是重點】*/
/*一個產品族由一個品牌工廠實作*/
//抽象產品的抽象工廠(抽象工廠生產抽象產品)
public interface IProductFactory {
//生成路由器
IRouterProduct routerProduct(); //回傳值是一個介面
//生產手機
IPhoneProduct phoneProduct();
}
//小米工廠生產小米族
public class XiaomiFactory implements IProductFactory{
@Override
public IRouterProduct routerProduct() {return new XiaomiRouter();} //需要回傳介面,但由于回傳的物體類實作了介面,所以最侄訓傳的可以看作成介面
@Override
public IPhoneProduct phoneProduct() {return new XiaomiPhone();}
}
//華為工廠生成華為族
public class HuaweiFactory implements IProductFactory{
@Override
public IRouterProduct routerProduct() {return new HuaweiRouter();}
@Override
public IPhoneProduct phoneProduct() {return new HuaweiPhone();}
}
/*一個產品等級就由一個產品介面*/
//手機產品介面
public interface IPhoneProduct {
void start();
void shutdown();
void wechat();
void email();
}
//路由器產品介面
public interface IRouterProduct {
void start();
void shutdown();
void openWifi();
void stopWifi();
}
//小米實作手機介面
public class XiaomiPhone implements IPhoneProduct {
@Override
public void start() {System.out.println("啟動小米手機");}
@Override
public void shutdown() {System.out.println("關閉小米手機");}
@Override
public void wechat() {System.out.println("使用小米手機聊天");}
@Override
public void email() {System.out.println("用小米手機發短信");}
}
//華為實作手機介面
public class HuaweiPhone implements IPhoneProduct{
@Override
public void start() {System.out.println("啟動華為手機");}
@Override
public void shutdown() {System.out.println("關倍訓為手機");}
@Override
public void wechat() {System.out.println("使用華為手機聊天");}
@Override
public void email() {System.out.println("用華為手機發短信");}
}
//小米實作路由器介面
public class XiaomiRouter implements IRouterProduct{
@Override
public void start() {System.out.println("啟動小米路由器");}
@Override
public void shutdown() {System.out.println("關閉小米路由器");}
@Override
public void openWifi() {System.out.println("打開小米路由器的wifi");}
@Override
public void stopWifi() {System.out.println("關閉小米路由器的wifi");}
}
//華為實作路由器介面
public class HuaweiRouter implements IRouterProduct{
@Override
public void start() {System.out.println("啟動華為路由器");}
@Override
public void shutdown() {System.out.println("關倍訓為路由器");}
@Override
public void openWifi() {System.out.println("打開華為路由器的wifi");}
@Override
public void stopWifi() {System.out.println("關倍訓為路由器的wifi");}
}
建造者模式
當構造物件時引數較多,可以通過建造者模式使用鏈式方法創建物件,保證引數填寫正確,
可以去看看StringBuilder的原始碼,有很多的框架都為我們提供了形如XXXBuilder的型別,我們一般也是使用這些類來創建我們需要的物件,
建造者模式創建物件其實和StringBuilder一樣:實際上我們是通過建造者來不斷配置引數或是內容,當我們配置完所有內容后,最后再進行物件的構建,
public static void main(String[] args) {
StringBuilder builder = new StringBuilder(); //創建一個StringBuilder來逐步構建一個字串
builder.append(666); //拼接一個數字
builder.append("老鐵"); //拼接一個字串
builder.insert(2, '?'); //在第三個位置插入一個字符
System.out.println(builder.toString()); //差不多成形了,最后轉換為字串
}
舉例:
//物體類的撰寫
public class Student {
int id;
int age;
int grade;
String name;
String college;
String profession;
List<String> awards;
//一律使用建造者來創建,不對外直接開放
private Student(int id, int age, int grade, String name, String college, String profession, List<String> awards) {
this.id = id;
this.age = age;
this.grade = grade;
this.name = name;
this.college = college;
this.profession = profession;
this.awards = awards;
}
public static StudentBuilder builder(){ //通過builder方法直接獲取建造者
return new StudentBuilder();
}
public static class StudentBuilder{ //這里就直接創建一個內部類
//Builder也需要將所有的引數都進行暫時保存,所以Student怎么定義的這里就怎么定義
int id;
int age;
int grade;
String name;
String college;
String profession;
List<String> awards;
public StudentBuilder id(int id){ //直接呼叫建造者對應的方法,為對應的屬性賦值
this.id = id;
return this; //為了支持鏈式呼叫,這里直接回傳建造者本身,下同
}
public StudentBuilder age(int age){
this.age = age;
return this;
}
...
public StudentBuilder awards(String... awards){
this.awards = Arrays.asList(awards);
return this;
}
public Student build(){ //最后我們只需要呼叫建造者提供的build方法即可根據我們的配置回傳一個物件
return new Student(id, age, grade, name, college, profession, awards);
}
}
}
//主方法
public static void main(String[] args) {
Student student = Student.builder() //獲取建造者
.id(1) //逐步配置各個引數
.age(18)
.grade(3)
.name("小明")
.awards("ICPC-ACM 區域賽 金牌", "LPL 2022春季賽 冠軍")
.build(); //最后直接建造我們想要的物件
}
單例模式
單例模式:在計算機行程中,同一個類始終只會有一個物件來進行操作,
多例模式:在計算機行程中,對一個物體類創建一次物件就是對當個物件操作,若是創建多個物件則是分別對對應的物件操作,
單例模式的三種寫法:
-
餓漢式單例(不建議)
在最開始就創建了物件(太饑渴了,一開始就需要物件)
public class Singleton { private final static Singleton INSTANCE = new Singleton(); //用于參考全域唯一的單例物件,在一開始就創建好 private Singleton() {} //禁用了構造方法Singleton()來創建物件,不允許隨便new,需要物件直接找getInstance public static Singleton getInstance(){ //獲取全域唯一的單例物件 return INSTANCE; } } -
加鎖的懶漢式單例(不建議,沒有第三種方法好)
懶漢:在要用的時候才創建物件,但又得防多執行緒就上了鎖
public class Singleton { private static volatile Singleton INSTANCE; //在一開始先不進行物件創建,volatile關鍵字是多執行緒的時候,這個變數更改了,別的執行緒可以立馬檢測到 private Singleton() {} //禁用了構造方法Singleton()來創建物件,不允許隨便new,需要物件直接找getInstance public static Singleton getInstance(){ if(INSTANCE == null) { //這層判斷是便于第一次外訪問時不用在走鎖 synchronized (Singleton.class) { //加鎖是為了防止多執行緒創建了多個物件 if(INSTANCE == null) INSTANCE = new Singleton(); //由于加了鎖,所以當一個行程進來創建了物件,其他執行緒需要再判斷一次有沒有人已經創建了這個類物件,有就不創建了,內層還要進行一次檢查,雙重檢查鎖定 } } return INSTANCE; } } -
靜態內部類的半懶、半餓式單例(建議)
靜態內部類剛開始不會加載,需要的時候才會加載,由于這個類一加載就會創建物件,
所以實作了懶漢的資源不濫用,餓漢的防止多執行緒
public class Singleton { private Singleton() {}//禁用了構造方法Singleton()來創建物件,不允許隨便new,需要物件直接找getInstance private static class Holder { //由靜態內部類持有單例物件,但是根據類加載特性,我們僅使用Singleton類時,不會對靜態內部類進行初始化,一旦類初始化之后值將不會改變,有點餓漢式的味道, private final static Singleton INSTANCE = new Singleton(); } public static Singleton getInstance(){ //只有真正使用內部類時,才會進行類初始化 return Holder.INSTANCE; // } }
原型模式
定義:用原型實體指定創建物件的種類,并且通過拷貝這些原型創建新的物件,(說白了就是復制)
- 淺拷貝:①對于類中基本資料型別,會直接復制值給拷貝物件;②對于參考型別(物件型別),只會復制物件的地址,而實際上指向的還是原來的那個物件,拷貝個寂寞,
public static void main(String[] args) {
int a = 10;
int b = a; //基本型別淺拷貝
System.out.println(a == b); //true
Object o = new Object();
Object k = o; //參考型別淺拷貝,拷貝的僅僅是對上面物件的參考
System.out.println(o == k); //true
}
- 深拷貝:無論是基本型別還是參考型別,深拷貝會將參考型別的所有內容,全部拷貝為一個新的物件,包括物件內部的所有成員變數,也會進行拷貝,
使用Cloneable介面提供的拷貝機制,來實作原型模式:操作完會發現Object的clone默認還是淺復制
protected class Student implements Cloneable{ //注意需要實作Cloneable介面
...
//Cloneable中的方法,下面代碼復制Object的clone原始碼
@Override
public Object clone() throws CloneNotSupportedException { //提升clone方法的訪問權限
return super.clone();
}
}
//主方法
public static void main(String[] args) throws CloneNotSupportedException {
Student student0 = new Student();
Student student1 = (Student) student0.clone();
System.out.println(student0);
System.out.println(student1);
//兩個結果不同,就是地址不同
Student student0 = new Student("小明");
Student student1 = (Student) student0.clone();
System.out.println(student0.getName() == student1.getName());
//true
}
深拷貝:在student實作介面Cloneable后重寫clone方法
@Override
public Object clone() throws CloneNotSupportedException { //這里我們改進一下,針對成員變數也進行拷貝
Student student = (Student) super.clone();
student.name = new String(name);
return student; //成員拷貝完成后,再回傳
}
結構性設計模式
針對類與物件的組織結構,(白話:類與物件之間的互動的多種模式
類/物件配接器模式
當需要傳入一個A型別引數,但只有B型別類時,就需要一個A型別的配接器裝入B類的資料,來將B資料轉成A型別,然后作為引數傳入
配接器,在生活中又稱轉換器,現在的手機基本都割去了3.5mm的耳機介面,此時只有有線耳機,要聽歌就需要一個轉換器將3.5mm介面轉成手機有的type-c的介面
類配接器(不建議)
繼承需要轉變的類,實作需要轉成的型別介面
//主方法
public class Main {
public static void main(String[] args) {
TestSupplier supplier = new TestSupplier();
test( ? ); //我們沒有35MM型別的手機介面,只有type-c的手機介面,那這里該填個type-c,所以需要一個轉介面將35MM轉為type-c介面
}
public static void test(typeC typec){ //現在我們需要呼叫test方法,但是test方法需要型別是typeC
System.out.println("成功得到:"+typec.listen());
}
}
//介面
public interface typeC { //typeC介面也想聽歌
String listen();
}
//父類
public class 35MM{
public String listenMusic(){
return "有線耳機聽歌!" //因為只有有線耳機,所以只有35MM才能聽歌
}
}
//子類作配接器 繼承35MM,實作type-C介面
public class Adapter extends 35MM implements typeC{
@Override
public String listen() { //現在不再繼承35MM,僅實作typeC介面
return super.listenMusic();
}
}
物件配接器
實作需要轉成的型別介面,將需要轉變的類實體化,并用作與配接器類的構造方法
因為類配接器會占用一個繼承位,而java又是單繼承的,如果typeC不是介面而是抽象類的話就用不了了,所以提出物件配接器:
//主方法
public class Main {
public static void main(String[] args) {
TestSupplier supplier = new TestSupplier();
test( ? ); //我們沒有35MM型別的手機介面,只有type-c的手機介面,那這里該填個type-c,所以需要一個轉介面將35MM轉為type-c介面
}
public static void test(typeC typec){ //現在我們需要呼叫test方法,但是test方法需要型別是typeC
System.out.println("成功得到:"+typec.listen());
}
}
//介面
public interface typeC { //typeC介面也想聽歌
String listen();
}
//父類
public class 35MM{
public String listenMusic(){
return "有線耳機聽歌!" //因為只有有線耳機,所以只有35MM才能聽歌
}
}
//子類作配接器 繼承35MM,實作type-C介面
public class Adapter implements typeC{ //現在不再繼承35MM,僅實作typeC介面
35MM 35mm; //實體化需要轉變的類
public Adapter(35MM 35mm){ //將實體化的物件用于構造物件
this.35mm = 35mm;
}
@Override
public String listen() { //接著實作listen方法,直接使用typeC提供的實作
return 35mm.listenMusic();
}
}
橋接模式
選擇不同的配置(零件)組成一個東西(先組一個再組一個,一層一層的基礎來達到組零件的目的)
同一種產品有著不同的配置,就像手機有:運行記憶體 4 6 8g,存盤記憶體:64 128 256g,芯片:驍龍 A系列 麒麟 聯發科 獵戶座,不能每一種配置都寫一個類就太麻煩了,所以有了橋接模式,可以通過多個類橋接成一個產品類,
優勢:可以通過多個維度來自由設定配置
這里以華為手機舉例:(小知識——華為手機是用自家的麒麟芯片)
//第一層類:繼承該類可以自定義芯片型別
public abstract class AbstractPhone {
private Size size; //這里是描述存盤記憶體,由于舉例簡單點方便看得懂就不寫運行記憶體了
public AbstractPhone(Size size){
this.size = size;
}
public abstract String getType(); //這里的型別是指芯片型別
}
//介面及實作類
public interface Size{
String getSize();
}
public class 256G implements Size{
@Override
public String getSize() {
return "256g記憶體";
}
}
//第二層類:繼承該類可以自定義芯片型別和存盤記憶體的尺度大小
public abstract class RefinedAbstractPhone extends AbstractPhone{
protected RefinedAbstractPhone(Size size) {
super(size);
}
public String getSize(){ //添加尺寸維度獲取方式
return size.getSize();
}
}
//產品類:繼承第二層類,然后自定義存盤記憶體大小和芯片種類
public class HUAWEI extends RefinedAbstractPhone{
protected HUAWEI(Size size){ //構造方法指定具體存盤記憶體大小
super(size);
}
@Override
public String getType() {
return "華為手機"; //回傳手機品牌型別
}
}
//主方法
public static void main(String[] args) {
HUAWEI huawei = new HUAWEI(new 256G());
System.out.println(huawei.getType());
System.out.println(huawei.getSize());
}
組合模式
對多個組件進行統一一樣的操作
組合模式實際上就是將多個組件進行組合,讓用戶可以對它們進行一致性處理,比如我們的檔案夾,一個檔案夾中可以有很多個子檔案夾或是檔案,
它就像是一個樹形結構一樣,有分支有葉子,而組合模式則是可以對整個樹形結構上的所有節點進行遞回處理,比如我們現在希望將所有檔案夾中的檔案的名稱前面都添加一個前綴,那么就可以使用組合模式,

組合模式的示例如下,這里我們就用檔案和檔案夾的例子來講解:
/**
* 首先創建一個組件抽象,組件可以包含組件,組件有自己的業務方法
*/
public abstract class Component {
public abstract void addComponent(Component component); //添加子組件
public abstract void removeComponent(Component component); //洗掉子組件
public abstract Component getChild(int index); //獲取子組件
public abstract void test(); //執行對應的業務方法,比如修改檔案名稱
}
接著我們來撰寫兩種實作類:檔案夾實作類,檔案實作類
public class Directory extends Component{ //目錄可以包含多個檔案或目錄
List<Component> child = new ArrayList<>(); //這里我們使用List來存放目錄中的子組件
@Override
public void addComponent(Component component) {
child.add(component);
}
@Override
public void removeComponent(Component component) {
child.remove(component);
}
@Override
public Component getChild(int index) {
return child.get(index);
}
@Override
public void test() {
child.forEach(Component::test); //將繼續呼叫所有子組件的test方法執行業務
}
}
public class File extends Component{ //檔案就相當于是樹葉,無法再繼續添加子組件了
@Override
public void addComponent(Component component) {
throw new UnsupportedOperationException(); //不支持這些操作了
}
@Override
public void removeComponent(Component component) {
throw new UnsupportedOperationException();
}
@Override
public Component getChild(int index) {
throw new UnsupportedOperationException();
}
@Override
public void test() {
System.out.println("檔案名稱修改成功!"+this); //具體的名稱修改操作
}
}
最后,我們來測驗一下:可以看到我們對最外層目錄進行操作后,會遞回向下處理當前目錄和子目錄中所有的檔案
public static void main(String[] args) {
Directory outer = new Directory(); //新建一個外層目錄
Directory inner = new Directory(); //新建一個內層目錄
outer.addComponent(inner);
outer.addComponent(new File()); //在內層目錄和外層目錄都添加點檔案,注意別導錯包了
inner.addComponent(new File());
inner.addComponent(new File());
outer.test(); //開始執行檔案名稱修改操作
}
裝飾模式
通過B類 實作對A類方法執行前后,分別多執行一些操作,類似于AOP

Base是抽象類/介面,有一個實作類實作其里面具體的業務方法,Decorator可以理解為是一個給裝飾者們的一個抽象類,然后不同的裝飾者再去具體繼承Decorator并在業務方法前后進行修飾,
適用:業務功能前后實作一些操作,如:在支付前提醒是否需要支付xxx元,
//頂層抽象類
public abstract class Base { //頂層抽象類,定義了一個test方法執行業務
public abstract void test();
}
//業務實作類
public class BaseImpl extends Base{
@Override
public void test() {
System.out.println("我是業務方法"); //具體的業務方法
}
}
//裝飾業務類(這里的構造方法引數是需要傳入實作業務類物件)
public class Decorator extends Base{ //裝飾者需要將裝飾目標組合到類中
protected Base base;
public Decorator(Base base) {
this.base = base;
}
@Override
public void test() {
base.test(); //這里暫時還是使用目標的原本方法實作
}
}
//具體實作裝飾業務類
public class DecoratorImpl extends Decorator{ //裝飾實作
public DecoratorImpl(Base base) {
super(base);
}
@Override
public void test() { //對原本的方法進行裝飾,我們可以在前后都去添加額外操作
System.out.println("裝飾方法:我是操作前邏輯");
super.test();
System.out.println("裝飾方法:我是操作后邏輯");
}
}
//主方法
public static void main(String[] args) {
Base base = new BaseImpl();
Decorator decorator = new DecoratorImpl(base); //將Base實作裝飾一下
Decorator outer = new DecoratorImpl(decorator); //裝飾者還可以嵌套,此時是裝飾兩次
decorator.test(); //裝飾一次:裝飾前——業務方法——裝飾后
outer.test(); //裝飾兩次:裝飾前——裝飾前——業務方法——裝飾后——裝飾后
}
代理模式
和裝飾模式代碼一模一樣,但核心是思想不同

代理模式是講DecoratorImpl給別人代理了,裝飾模式是DecoratorImpl自己增強,
裝飾模式和代理模式:
- 結構相同:都實作同一個介面/抽象類
- 作用不同:
- 裝飾器模式強調的是增強自身,在被裝飾之后你能夠在被增強的類上使用增強后的功能,增強后你還是你,只不過被強化了而已;
- 代理模式強調要讓別人幫你去做事情,以及添加一些本身與你業務沒有太多關系的事情(記錄日志、設定快取等)重點在于讓別人幫你做,
代理模式一般代碼:
//頂層抽象類
public abstract class Base { //頂層抽象類,定義了一個test方法執行業務
public abstract void test();
}
//業務實作類
public class BaseImpl extends Base{
@Override
public void test() {
System.out.println("我是業務方法"); //具體的業務方法
}
}
//代理業務類(這里的構造方法引數是需要傳入實作業務類物件)
public class Decorator extends Base{ //代理者需要將代理目標組合到類中
protected Base base;
public Decorator(Base base) {
this.base = base;
}
@Override
public void test() {
base.test(); //這里暫時還是使用目標的原本方法實作
}
}
//具體實作代理業務類
public class DecoratorImpl extends Decorator{ //代理實作
public DecoratorImpl(Base base) {
super(base);
}
@Override
public void test() { //對原本的方法進行代理,我們可以在前后都去添加額外操作
System.out.println("裝飾方法:我是操作前邏輯");
super.test();
System.out.println("裝飾方法:我是操作后邏輯");
}
}
//主方法
public static void main(String[] args) {
Base base = new BaseImpl();
Decorator decorator = new DecoratorImpl(base); //將Base實作代理一下
Decorator outer = new DecoratorImpl(decorator); //代理者還可以嵌套,此時是代理兩次
decorator.test(); //代理一次:代理前——業務方法——代理后
outer.test(); //代理兩次:代理前——代理前——業務方法——代理后——代理后
}
實作代理模式除了和裝飾模式一樣的代碼情況外還有兩種實作方式:【因為都是動態代理所以生成的代理類是看不到的】
-
JDK提供的動態代理:我們不再需要手動撰寫繼承關系創建代理類,它能夠在運行時通過反射機制為我們自動生成代理類:【只能代理介面】
//介面 public interface Subject { //JDK提供的動態代理只支持介面 void test(); } //介面實作類 public class SubjectImpl implements Subject{ @Override public void test() { System.out.println("我是測驗方法!"); } } //創建動態代理的處理邏輯(就是執行業務前后的方法撰寫在里面) public class TestProxy implements InvocationHandler { //代理類,需要實作InvocationHandler介面 private final Object object; //這里需要保存一下被代理的物件,下面需要用到 public TestProxy(Object object) { this.object = object; } @Override //此方法就是呼叫代理物件的對應方法時會進入,這里我們就需要撰寫如何進行代理了 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //method就是呼叫的代理物件的哪一個方法,args是實引陣列 System.out.println("代理的物件:"+proxy.getClass()); //proxy就是生成的代理物件了,我們看看是什么型別的 Object res = method.invoke(object, args); //在代理中呼叫被代理物件原本的方法,因為你是代理,還是得執行一下別人的業務,當然也可以不執行,但是這樣就失去代理的意義了,注意要用上面的object System.out.println("方法呼叫完成,回傳值為:"+res); //看看回傳值是什么 return res; //回傳回傳值 } } -
Spring在使用的CGLib框架代理,
maven依賴:
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.1</version> </dependency>代碼實作:
//介面 public interface Subject { //JDK提供的動態代理只支持介面 void test(); } //介面實作類 public class SubjectImpl implements Subject{ @Override public void test() { System.out.println("我是測驗方法!"); } } //創建動態代理的處理邏輯(就是執行業務前后的方法撰寫在里面) public class TestProxy implements MethodInterceptor { //首先還是撰寫我們的代理邏輯 private final Object target; //這些和之前JDK動態代理寫法是一樣的 public TestProxy(Object target) { this.target = target; } @Override //我們也是需要在這里去撰寫我們的代理邏輯 public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("現在是由CGLib進行代理操作!"+o.getClass()); return method.invoke(target, objects); //也是直接呼叫代理物件的方法即可 } } //主方法 public static void main(String[] args) { SubjectImpl subject = new SubjectImpl(); Enhancer enhancer = new Enhancer(); //增強器,一會就需要依靠增強器來為我們生成動態代理物件 enhancer.setSuperclass(SubjectImpl.class); //直接選擇我們需要代理的型別,直接不需要介面或是抽象類,SuperClass作為代理類的父類存在,這樣我們就可以按照指定型別的方式去操作代理類了 enhancer.setCallback(new TestProxy(subject)); //設定我們剛剛撰寫好的代理邏輯 SubjectImpl proxy = (SubjectImpl) enhancer.create(); //直接創建代理類 proxy.test(); //呼叫代理類的test方法 }
外觀模式
可以理解為門面模式,將需要通過操作多個類實作的一個功能封裝到一個類中,便于使用
當每個功能是一個系統,完成一個業務需要多個功能時就需要分別呼叫多個系統,此時就可以將一個業務需要使用的多個系統封裝成一個門面系統,只要呼叫該門面系統即可完成該業務,

舉例:比如現在我們設計了三個子系統,分別是排隊、結婚、領證,正常情況下我們是需要分別去找這三個部門去完成的,但是現在我們通過門面統一來完成
//系統一
public class SubSystemA {
public void test1(){
System.out.println("排隊");
}
}
//系統二
public class SubSystemB {
public void test2(){
System.out.println("結婚");
}
}
//系統三
public class SubSystemC {
public void test3(){
System.out.println("領證");
}
}
//門面
public class Facade {
SubSystemA a = new SubSystemA();
SubSystemB b = new SubSystemB();
SubSystemC c = new SubSystemC();
public void marry(){ //紅白喜事一條龍服務
a.test1();
b.test2();
c.test3();
}
}
//主方法
public static void main(String[] args) {
Facade facade = new Facade();
facade.marry();
}
享元模式
核心是共享,當A類方法里寫了一個方法,B類中需要同樣的方法就可以直接創建A類物件來呼叫方法或者通過一個方法工廠類收集各個方法,然后B類通過工廠類呼叫A類方法
舉例:通過享元工廠類實作共享方法
//A類
public class DBUtil {
public void selectDB(){
System.out.println("我是資料庫操作...");
}
}
//享元工廠
public class DBUtilFactory {
private static final DBUtil UTIL = new DBUtil(); //享元物件被存放在工廠中
public static DBUtil getFlyweight(){ //獲取享元物件
return UTIL;
}
}
//B類
public class UserService { //用戶服務
public void service(){
DBUtil util = DBUtilFactory.getFlyweight(); //通過享元工廠拿到DBUtil物件
util.selectDB(); //該干嘛干嘛
}
}
行為型設計模式
針對物件之間的互動
解釋器模式
java中用的很多,JVM編譯的時候就是對我們寫的代碼進行了解釋操作;資料庫SQL陳述句亦是如此
解釋器:對語言進行解釋,根據不同語意來做不同的事情,
舉例:雙堆疊計算器
public class 雙堆疊實作計算器 {
//設定兩堆疊
private static Deque<Character> opr = new LinkedList<>();
private static Deque<Double> number = new LinkedList<>();
public static void main(String[] args) {
//接收一串字串并轉字符陣列
Scanner scanner = new Scanner(System.in);
String str = scanner.nextLine();
char [] arrC = str.toCharArray();
for (int i = 0; i < arrC.length;) {
char c = arrC[i];
//是+-/*字符時
if (isChar(c)){
///如果堆疊內有字符,則需要判斷優先級,入堆疊字符小于等于堆疊內字符則需要先計算堆疊內字符
Character peek = opr.peek(); //細節!!! 這里必須先取
while (peek!=null && isLowIn(c,peek)){
cal();
peek=opr.peek(); //細節!!這里也必須更新peek
}
//入堆疊
opr.push(c);
i++;
}
//字符是數字時
else {
double sum=0; //接收整數和
double sum2=0; //接收小數和
int times = 1; //記錄當前小數位數
boolean flag=false; //是否開啟小數檢測模式
//判斷下一個是不是+-*/,不是的話就繼續判斷直到非數字
while (i<=arrC.length-1 && !isChar(arrC[i])){ //細節:括號內兩者順序不能改變
//遇到小數的情況
if (arrC[i]=='.'){
flag=true;
}else {
//小數情況
if (flag){
double val=arrC[i]-'0';
for (int j = 0; j < times; j++) { //細節!用times縮小值
val/=10.0;
}
times++;
sum2+=val;
}
//正數情況
else {
sum=sum*10+arrC[i]-'0'; //獲取多位數字的關鍵!!!
}
}
i++;
}
number.push(sum+sum2);
}
}
//字符都獲取完了后堆疊內還有數字和字符的話,就計算完堆疊內的資料并輸出最終結果
while (!opr.isEmpty()) cal();
System.out.println(number.peek());
}
//判斷是否為字符
public static boolean isChar(char c) {
return c=='+'||c=='-'||c=='*'||c=='/';
}
//判斷優先級是否是堆疊外字符小于等于堆疊內字符
public static boolean isLowIn(char out,char in){
return (out=='+'||out=='-')||(in=='*'||in=='/');
}
public static void cal(){
//從堆疊內取出兩個陣列和一個字符
double a = number.pop();
double b = number.pop();
char c = opr.poll();
//根據字符c進行不同的運算
switch (c){
case '+':
number.push(a+b);
break;
case '-':
number.push(b-a);
break;
case '*':
number.push(b*a);
break;
case '/':
number.push(b/a);
break;
default:
System.out.println("字符輸入有誤");
}
}
}
模板方法模式
在執行一類業務時前面有很多步驟都是相同時,就可以寫一個模板抽象類,留出一個方法去給子類定義業務最后的操作,
該模式在原始碼中大量的被應用,這樣寫會給后期維護提供非常清晰的思路
舉例:去醫院看病,掛號和看醫生是固定模式,但后面要不要開處方藥和拿藥是不一定的
//模板抽象類
/**
* 抽象診斷方法,因為現在只知道掛號和看醫生是固定模式,剩下的開處方和拿藥都是不確定的
*/
public abstract class AbstractDiagnosis {
public void test(){
System.out.println("今天頭好暈,不想起床,開擺,先跟公司請個假");
System.out.println("去醫院看病了~");
System.out.println("1 >> 先掛號");
System.out.println("2 >> 等待叫號");
//由于現在不知道該開什么處方,所以只能先定義一下行為,然后具體由子類實作
//大致的流程先定義好就行
this.prescribe();
this.medicine(); //開藥同理
}
public abstract void prescribe(); //開處方操作根據具體病癥決定了
public abstract void medicine(); //拿藥也是根據具體的處方去拿
}
//實作具體業務的子類
/**
* 感冒相關的具體實作子類
*/
public class ColdDiagnosis extends AbstractDiagnosis{
@Override
public void prescribe() {
System.out.println("3 >> 一眼丁真,鑒定為假,你這不是感冒,純粹是想擺爛");
}
@Override
public void medicine() {
System.out.println("4 >> 開點頭孢回去吃吧");
}
}
//主方法
public static void main(String[] args) {
AbstractDiagnosis diagnosis = new ColdDiagnosis();
diagnosis.test();
}
責任鏈模式
就像闖關,一層接一層的往下進行,可以理解為報銷的時候需要一層一層審批
比如JavaWeb中學習的Filter過濾器,正是采用的責任鏈模式,通過將請求一級一級不斷向下傳遞,來對我們所需要的請求進行過濾和處理,

舉例:這里就使用責任鏈模式來模擬一個簡單的面試程序,面試也是一面二面三面這樣走的流程,這里先設計一下責任鏈上的各個處理器
//設計模板抽象方法,并在此基礎上寫層層往下的責任鏈
public abstract class Handler {
protected Handler successor; //這里我們就設計責任鏈以單鏈表形式存在,這里存放后繼節點
public Handler connect(Handler successor){ //拼接后續節點
this.successor = successor;
return successor; //這里回傳后繼節點,方便我們一會鏈式呼叫
}
public void handle(){
this.doHandle(); //由不同的子類實作具體處理程序
Optional
.ofNullable(successor) //設定可以為null
.ifPresent(Handler::handle); //責任鏈上如果還有后繼節點,就繼續向下傳遞
}
public abstract void doHandle(); //結合上節課學習的模板方法,交給子類實作
}
//一面子類
public class FirstHandler extends Handler{ //用于一面的處理器
@Override
public void doHandle() {
System.out.println("============= 白馬程式員一面 ==========");
System.out.println("1. 談談你對static關鍵字的理解?");
System.out.println("2. 內部類可以呼叫外部的資料嗎?如果是靜態的呢?");
System.out.println("3. hashCode()方法是所有的類都有嗎?默認回傳的是什么呢?");
System.out.println("以上問題會的,可以依次打在評論區");
}
}
//二面子類
public class SecondHandler extends Handler{ //二面
@Override
public void doHandle() {
System.out.println("============= 白馬程式員二面 ==========");
System.out.println("1. 如果我們自己創建一個java.lang包并且撰寫一個String類,能否實作覆寫JDK默認的?");
System.out.println("2. HashMap的負載因子有什么作用?變化規律是什么?");
System.out.println("3. 執行緒池的運作機制是什么?");
System.out.println("4. ReentrantLock公平鎖和非公平鎖的區別是什么?");
System.out.println("以上問題會的,可以依次打在評論區");
}
}
//三面子類
public class ThirdHandler extends Handler{
@Override
public void doHandle() {
System.out.println("============= 白馬程式員三面 ==========");
System.out.println("1. synchronized關鍵字了解嗎?如何使用?底層是如何實作的?");
System.out.println("2. IO和NIO的區別在哪里?NIO三大核心組件?");
System.out.println("3. TCP握手和揮手流程?少一次握手可以嗎?為什么?");
System.out.println("4. 作業系統中PCB是做什么的?運行機制是什么?");
System.out.println("以上問題會的,可以依次打在評論區");
}
}
//主方法
public static void main(String[] args) {
Handler handler = new FirstHandler(); //一面首當其沖
handler
.connect(new SecondHandler()) //繼續連接二面和三面
.connect(new ThirdHandler());
handler.handle(); //開始面試
}
命令模式
命令模式,此時會有三個頂層行為:遙控器、命令、接收器,
話不多說,直接搬例,小米家具就是典型的命令模式,只需要在手機(遙控器)上通過紅外線、藍牙等按下一些命令,家中的家具(接收器)就會執行命令,
舉例:設定三個頂層的 介面/抽象類 ,遙控器、命令、接收器
//遙控器
public class Controller { //遙控器只需要把我們的指令發出去就行了
public static void call(Command command){
command.execute();
}
}
//命令
public abstract class Command { //指令抽象,不同的電器有指令
private final Receiver receiver;
protected Command(Receiver receiver){ //指定此命令對應的電器(接受者)
this.receiver = receiver;
}
public void execute() {
receiver.action(); //執行命令,實際上就是讓接收者開始干活
}
}
//接收器
public interface Receiver {
void action(); //具體行為,這里就寫一個算了
}
//具體接收器
public class AirConditioner implements Receiver{
@Override
public void action() {
System.out.println("空調已開啟,呼呼呼");
}
}
//具體命令
public class OpenCommand extends Command {
public OpenCommand(AirConditioner airConditioner) {
super(airConditioner);
}
}
//可以創建具體控制器(手機),也可以不創建直接遙控,因為控制器一般只有一個,所以這里就不創建了
//主方法
public static void main(String[] args) {
AirConditioner airConditioner = new AirConditioner(); //先創建一個空調
Controller.call(new OpenCommand(airConditioner)); //直接通過遙控器來發送命令開啟空調
}
迭代器模式
每個集合類都有相應的迭代器,很少自定義,大都是用jdk定義好的迭代器
迭代器最直接的例子就是foreach語法糖,
public static void main(String[] args) {
List<String> list = Arrays.asList("AAA", "BBB", "CCC");
for (String s : list) { //使用foreach語法糖進行迭代,依次獲取每一個元素
System.out.println(s); //列印一下
}
}
上述代碼編譯后的樣子:
public static void main(String[] args) {
List<String> list = Arrays.asList("AAA", "BBB", "CCC");
Iterator var2 = list.iterator(); //實際上這里本質是通過List生成的迭代器來遍歷我們每個元素的
while(var2.hasNext()) { //判斷是否還有元素可以迭代,沒有就false
String s = (String)var2.next(); //通過next方法得到下一個元素,每呼叫一次,迭代器會向后移動一位
System.out.println(s); //列印一下
}
}
由此可知迭代器原理:使用迭代器對List進行遍歷時,實際上就像一個指向串列頭部的指標,我們通過不斷向后移動指標來依次獲取所指向的元素:


拓展:自定義迭代器
(由于每個迭代器需要根據不同的集合類特點來設計,所以自定義迭代器前需要自定義一個集合類)
//自定義集合類
public class ArrayCollection<T> { //首先設計一個簡單的陣列集合,一會我們就迭代此集合內的元素
private final T[] array; //底層使用一個陣列來存放資料
private ArrayCollection(T[] array){ //private掉,自己用
this.array = array;
}
public static <T> ArrayCollection<T> of(T[] array){ //開個靜態方法直接把陣列轉換成ArrayCollection,其實和直接new一樣,但是這樣寫好看一點
return new ArrayCollection<>(array);
}
}
//自定義迭代器
public class ArrayCollection<T> implements Iterable<T>{ //實作Iterable介面表示此類是支持迭代的
...
@Override
public Iterator<T> iterator() { //需要實作iterator方法,此方法會回傳一個迭代器,用于迭代我們集合中的元素
return new ArrayIterator();
}
public class ArrayIterator implements Iterator<T> { //這里實作一個,注意別用靜態,需要使用物件中存放的陣列
private int cur = 0; //這里我們通過一個指標表示當前的迭代位置
@Override
public boolean hasNext() { //判斷是否還有下一個元素
return cur < array.length; //如果指標大于或等于陣列最大長度,就不能再繼續了
}
@Override
public T next() { //回傳當前指標位置的元素并向后移動一位
return array[cur++]; //正常回傳對應位置的元素,并將指標自增
}
}
}
//主方法
public static void main(String[] args) {
String[] arr = new String[]{"AAA", "BBB", "CCC", "DDD"};
ArrayCollection<String> collection = ArrayCollection.of(arr);
for (String s : collection) { //可以直接使用foreach語法糖,當然最后還是會變成迭代器呼叫
System.out.println(s);
}
}
//編譯主方法后的樣子
public static void main(String[] args) {
String[] arr = new String[]{"AAA", "BBB", "CCC", "DDD"};
ArrayCollection<String> collection = ArrayCollection.of(arr);
Iterator var3 = collection.iterator(); //首先獲取迭代器,實際上就是呼叫我們實作的iterator方法
while(var3.hasNext()) {
String s = (String)var3.next(); //直接使用next()方法不斷向下獲取
System.out.println(s);
}
}
中介者模式
將多對多的復雜關系,變成一對多的簡單明了關系
話不多說,直接上例子,中介,第一個想到的就是房子的中介,當一堆人需要出租房屋、另一堆人又需要租房,如果沒有中介,那再好的房子很難遇上租客也租不出去,此時就需要一個中介將雙方的需要進行匹配從而實作房子出租的目的,
不找中介:很亂,還不一定能夠遇上需求相同的對方

找中介:中介將雙方整理好,進行需求匹配

舉例:中介、出租者、租房者
//中介
public class Mediator { //房產中介
private final Map<String, User> userMap = new HashMap<>(); //在出售的房子需要存盤一下
public void register(String address, User user){ //出售房屋的人,需要告訴中介他的房屋在哪里
userMap.put(address, user);
}
public User find(String address){ //通過此方法來看看有沒有對應的房源
return userMap.get(address);
}
}
//出租者和租房者(這里偷懶就寫在一起了)
public class User { //用戶可以是出售房屋的一方,也可以是尋找房屋的一方
String name;
String tel;
public User(String name, String tel) {
this.name = name;
this.tel = tel;
}
public User find(String address, Mediator mediator){ //找房子的話,需要一個中介和你具體想找的地方
return mediator.find(address);
}
@Override
public String toString() {
return name+" (電話:"+tel+")";
}
}
//主方法
public static void main(String[] args) {
User user0 = new User("劉女士", "10086"); //出租者
User user1 = new User("李先生", "10010"); //租房者
Mediator mediator = new Mediator(); //我是中介
mediator.register("廣州市天河區白馬程式員", user0); //出租人先把房子給中介掛上去
User user = user1.find("廣州市天河區非馬程式員", mediator); //租房者向指定中介找房子
if(user == null) System.out.println("沒有找到對應的房源");
System.out.println(user); //成功找到對應房源
}
備忘錄模式
我也稱其為
時光回溯模式,比較少用,大都是底層代碼才用
這個備忘錄不是我們平時用于記錄容易忘記的ddl,而是保存曾經某個時刻的狀態,后面有需要就恢復到該時刻的狀態
舉例:保存物件的狀態
//物件物體
public class Student {
private String currentWork; //當前正在做的事情
private int percentage; //當前的作業完成百分比
public void work(String currentWork) {
this.currentWork = currentWork;
this.percentage = new Random().nextInt(100);
}
@Override
public String toString() {
return "我現在正在做:"+currentWork+" (進度:"+percentage+"%)";
}
public State save(){
return new State(currentWork, percentage);
}
public void restore(State state){
this.currentWork = state.currentWork;
this.percentage = state.percentage;
}
}
//狀態保存類
public class State {
final String currentWork;
final int percentage;
State(String currentWork, int percentage) { //僅開放給同一個包下的Student類使用
this.currentWork = currentWork;
this.percentage = percentage;
}
}
//主方法
public static void main(String[] args) {
Student student = new Student();
student.work("學Java"); //開始學Java
System.out.println(student);
State savedState = student.save(); //保存一下當前的狀態
student.work("打電動"); //剛打開B站播放視頻,學一半開始擺爛了
System.out.println(student);
student.restore(savedState); //后悔浪費時間,回到上一個保存的狀態
System.out.println(student); //回到學Java的狀態
}
觀察者模式
觀察者模式可以實作監聽器機制,當物件發生改變時,觀察者能夠立即察覺到并進行一些聯動操作,很少用,大都是直接用監聽器
舉例:自定義觀察者
//觀察者介面
public interface Observer { //觀察者介面
void update(); //當物件有更新時,會回呼此方法
}
//被觀察者的物體
public class Subject {
private final Set<Observer> observerSet = new HashSet<>();
public void observe(Observer observer) { //添加觀察者
observerSet.add(observer);
}
public void modify() { //模擬物件進行修改
observerSet.forEach(Observer::update); //當物件發生修改時,會通知所有的觀察者,并進行方法回呼
}
}
//主方法
public static void main(String[] args) {
Subject subject = new Subject();
subject.observe(() -> System.out.println("我是一號觀察者!")); //這里是實作介面里的抽象方法 update()
subject.observe(() -> System.out.println("我是二號觀察者!"));
subject.modify();//修改了會呼叫前面實作的抽象方法 update()
}
JDK也提供了是實作觀察者模式的相關介面:
//繼承介面表示支持觀察者
import java.util.Observable; //java.util包下提供的觀察者抽象類
public class Subject extends Observable { //繼承此抽象類表示支持觀察者
public void modify(){
System.out.println("對物件進行修改!");
this.setChanged(); //當對物件修改后,需要setChanged來設定為已修改狀態
this.notifyObservers(new Date()); //使用notifyObservers方法來通知所有的觀察者
//注意只有已修改狀態下通知觀察者才會有效,并且可以給觀察者傳遞引數,這里傳遞了一個時間物件
}
}
//主方法
public static void main(String[] args) {
Subject subject = new Subject();
subject.addObserver((o, arg) -> System.out.println("監聽到變化,并得到引數:"+arg));
//注意這里的Observer是java.util包下提供的
subject.modify(); //進行修改操作
}
狀態模式
根據不同的狀態執行不同的行為
水在不同的溫度狀態會隨之改變,程式也可以達到某種狀態后就執行不同的行為
//列舉狀態
public enum State { //狀態直接使用列舉定義
NORMAL, LAZY
}
//物體類
public class Student {
private State state; //使用一個成員來存盤狀態
public void setState(State state) {
this.state = state;
}
public void study(){
switch (state) { //根據不同的狀態,學習方法會有不同的結果
case LAZY:
System.out.println("只要我不努力,老板就別想過上想要的生活,開擺!");
break;
case NORMAL:
System.out.println("拼搏百天,我要上清華大學!");
break;
}
}
}
//主方法
public static void main(String[] args) {
Student student = new Student();
student.setState(State.NORMAL); //先正常模式
student.study();
student.setState(State.LAZY); //開啟擺爛模式
student.study();
}
策略模式
和狀態模式代碼一樣,但狀態模式思想是:狀態是先天的設定,就像水不同溫度狀態不同,而策略模式思想是:策略需要根據不同情況制定的,
舉例:簡單的陣列排列
//策略介面(模板方法模式)
public interface Strategy { //策略介面,不同的策略實作也不同
Strategy SINGLE = Arrays::sort; //單執行緒排序方案
Strategy PARALLEL = Arrays::parallelSort; //并行排序方案
void sort(int[] array);
}
//排序類
public class Sorter {
private Strategy strategy; //策略
public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
public void sort(int[] array){
strategy.sort(array);
}
}
//主方法
public static void main(String[] args) {
Sorter sorter = new Sorter();
sorter.setStrategy(Strategy.PARALLEL); //指定為并行排序方案
sorter.sort(new int[]{9, 2, 4, 5, 1, 0, 3, 7});
}
訪問者模式
一件事情,一個訪問介面,訪問者實作該介面,但不同訪問者關心的事情著重點不同
舉例:假如你獲獎了還是國家級一等獎
//獎物體類
public class Prize { //獎
String name; //比賽名稱
String level; //等級
public Prize(String name, String level) {
this.name = name;
this.level = level;
}
public String getName() {
return name;
}
public String getLevel() {
return level;
}
}
//訪問者介面
public interface Visitor {
void visit(Prize prize); //visit方法來訪問我們的獎項
}
//不同訪問者
public class Teacher implements Visitor { //指導老師作為一個訪問者
@Override
public void visit(Prize prize) { //它只關心你得了什么獎以及是幾等獎,這也關乎老師的榮譽
System.out.println("你得獎是什么獎?"+prize.name);
System.out.println("你得了幾等獎?"+prize.level);
}
}
public class Boss implements Visitor{ //你的公司老板作為一個訪問者
@Override
public void visit(Prize prize) { //你的老板只關心這些能不能為公司帶來什么效益,獎本身并不重要
System.out.println("你的獎項大么,能夠為公司帶來什么效益么?");
System.out.println("還不如老老實實加班給我多干干,別去搞這些沒用的");
}
}
//主方法
public static void main(String[] args) {
Prize prize = new Prize("ACM國際大學生程式設計競賽","一等價");
Teacher teacher = new Teacher();
Boss boss = new Boss();
teacher.visit(prize);
boss.visit(prize);
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/539381.html
標籤:設計模式
