軟體設計模式(Design pattern),又稱設計模式,是一套被反覆使用、多數人知曉的、經過分類編目的、代碼設計經驗的總結。使用設計模式是為了可重用代碼、讓代碼更容易被他人理解、保證代碼可靠性、程式的重用性。 ...
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 { //用戶可以是