面向?qū)ο笏枷朐O(shè)計(jì)原則
在實(shí)際的開發(fā)中抬纸,我們要想更深入的了解面向?qū)ο笏枷肓捅仨毷煜で叭丝偨Y(jié)過的面向?qū)ο蟮乃枷氲脑O(shè)計(jì)原則,那么都有哪些原則呢湿故,我們就來了解一下
- 單一職責(zé)原則
- 其實(shí)就是開發(fā)人員經(jīng)常說的”高內(nèi)聚阿趁,低耦合”
- 開閉原則
- 核心思想是:一個對象對擴(kuò)展開放,對修改關(guān)閉坛猪。其實(shí)開閉原則的意思就是:對類的改動是通過增加代碼進(jìn)行的脖阵,而不是修改現(xiàn)有代碼。
- 里氏替換原則
- 核心思想:在任何父類出現(xiàn)的地方都可以用它的子類來替代墅茉。其實(shí)就是說:同一個繼承體系中的對象應(yīng)該有共同的行為特征命黔。
- 依賴注入原則
- 核心思想:要依賴于抽象,不要依賴于具體實(shí)現(xiàn)就斤。
- 接口分離原則
- 核心思想:不應(yīng)該強(qiáng)迫程序依賴它們不需要使用的方法纷铣。
- 迪米特原則
- 核心思想:一個對象應(yīng)當(dāng)對其他對象盡可能少的了解
設(shè)計(jì)模式
設(shè)計(jì)模式在我們開發(fā)中還是經(jīng)常用到的,那么战转,下面我們來學(xué)習(xí)一下
-
設(shè)計(jì)模式概述
- 設(shè)計(jì)模式(Design pattern)是一套被反復(fù)使用搜立、多數(shù)人知曉的、經(jīng)過分類編目的槐秧、代碼設(shè)計(jì)經(jīng)驗(yàn)的總結(jié)啄踊。使用設(shè)計(jì)模式是為了可重用代碼、讓代碼更容易被他人理解刁标、保證代碼可靠性颠通。
設(shè)計(jì)模式不是一種方法和技術(shù),而是一種思想
設(shè)計(jì)模式和具體的語言無關(guān)膀懈,學(xué)習(xí)設(shè)計(jì)模式就是要建立面向?qū)ο蟮乃枷攵倜蹋M可能的面向接口編程,低耦合启搂,高內(nèi)聚硼控,使設(shè)計(jì)的程序可復(fù)用學(xué)習(xí)設(shè)計(jì)模式能夠促進(jìn)對面向?qū)ο笏枷氲睦斫猓粗嗳桓於摹K鼈兿噍o相成
設(shè)計(jì)模式的幾個要素:
名字 必須有一個簡單牢撼,有意義的名字
問題 描述在何時使用模式
解決方案 描述設(shè)計(jì)的組成部分以及如何解決問題
效果 描述模式的效果以及優(yōu)缺點(diǎn)
設(shè)計(jì)模式的分類
創(chuàng)建型模式 對象的創(chuàng)建
結(jié)構(gòu)型模式 對象的組成(結(jié)構(gòu))
行為型模式 對象的行為
創(chuàng)建型模式:簡單工廠模式,工廠方法模式疑苫,抽象工廠模式熏版,建造者模式纷责,原型模式,單例模式撼短。(6個)結(jié)構(gòu)型模式:外觀模式再膳、適配器模式、代理模式曲横、裝飾模式喂柒、橋接模式、組合模式胜榔、享元模式。(7個)行為型模式:模版方法模式湃番、觀察者模式夭织、狀態(tài)模式、職責(zé)鏈模式吠撮、命令模式尊惰、訪問者模式、策略模式泥兰、備忘錄模式弄屡、迭代器模式、解釋器模式鞋诗。(10個)
設(shè)計(jì)模式的分類雖然很多膀捷,但是我們平時能用到的也就下面幾種而已,所以不要被它這么多模式所嚇倒削彬。
-
常見的設(shè)計(jì)模式 簡單工廠模式和工廠方法模式(接口)
- 模版設(shè)計(jì)模式(抽象類)
- 裝飾設(shè)計(jì)模式(IO流)
- 單例設(shè)計(jì)模式(多線程)
- 適配器模式(GUI)
下面我們就來學(xué)習(xí)我們常用的幾種設(shè)計(jì)模式全庸,讓大家掌握,熟悉融痛,并運(yùn)用到自己的項(xiàng)目中壶笼,學(xué)會學(xué)以致用。
簡單工廠模式
- 簡單工廠模式概述
- 又叫靜態(tài)工廠方法模式雁刷,它定義一個具體的工廠類負(fù)責(zé)創(chuàng)建一些類的實(shí)例
我們來寫一個簡單的例子來解釋簡單工廠模式
/* * 抽象的動物類覆劈,里面有抽象的方法 */
public abstract class Animal {
public abstract void eat();
}
/* * 具體的動物貓繼承抽象動物類,重寫抽象方法 */
public class Cat extends Animal {
@Override
public void eat() {
System.out.println("貓吃魚");
}
}
/* * 具體的動物狗繼承抽象動物類沛励,重寫抽象方法 */
public class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃肉");
}
}
/* * 動物工廠類责语,可以造貓和狗 */
public class AnimalFactory {
private AnimalFactory() { }
public static Animal createAnimal(String type) {
if ("dog".equals(type)) {
return new Dog();
} else if ("cat".equals(type)) {
return new Cat();
} else {
return null;
}
}
}
/* * 測試類 */
public class AnimalDemo {
public static void main(String[] args) {
// 工廠有了后,通過工廠給造
Animal a = AnimalFactory.createAnimal("dog");
a.eat();
a = AnimalFactory.createAnimal("cat");
a.eat();
// NullPointerException
a = AnimalFactory.createAnimal("pig");
if (a != null) {
a.eat();
} else {
System.out.println("對不起目派,暫時不提供這種動物");
}
}
}
以前我們在學(xué)代碼的時候鹦筹,不會去創(chuàng)建這個動物的工廠類,而是直接具體類的調(diào)用址貌,比如:
Dog d = new Dog();
d.eat();
Cat c = new Cat();
c.eat();
現(xiàn)在我們運(yùn)用了簡單工廠模式后铐拐,就不用每次用的時候去new對象徘键,而是直接去調(diào)用這個工廠類里面的具體方法,它會給我們返回一個已經(jīng)new好的對象遍蟋。那么這樣做有什么有缺點(diǎn)呢吹害,我們來總結(jié)一下。
- 優(yōu)點(diǎn)
- 客戶端不需要在負(fù)責(zé)對象的創(chuàng)建虚青,從而明確了各個類的職責(zé)
- 缺點(diǎn)
- 這個靜態(tài)工廠類負(fù)責(zé)所有對象的創(chuàng)建它呀,如果有新的對象增加,或者某些對象的創(chuàng)建方式不同棒厘,就需要不斷的修改工廠類纵穿,不利于后期的維護(hù)
當(dāng)我們要用一個模式時,當(dāng)這個模式的優(yōu)點(diǎn)大于缺點(diǎn)的時候奢人,我們就可以使用了谓媒,但是在簡單工廠模式中我們可以看到它的缺點(diǎn),當(dāng)我們有新的對象增加時何乎,就要不斷的修改工廠類句惯,所以不推薦大家用簡單工廠模式,那么我們要用什么呢支救,這就引出了我們要學(xué)的下一個知識點(diǎn)工廠方法模式
工廠方法模式
- 工廠方法模式概述
- 工廠方法模式中抽象工廠類負(fù)責(zé)定義創(chuàng)建對象的接口抢野,具體對象的創(chuàng)建工作由繼承抽象工廠的具體類實(shí)現(xiàn)。
我們就來用工廠方法模式對上面的那個例子進(jìn)行改進(jìn)
/* * 抽象的動物類各墨,里面有抽象的方法 */
public abstract class Animal {
public abstract void eat();
}
/* * 工廠類接口指孤,里面有抽象的創(chuàng)造動物的方法 */
public interface Factory {
public abstract Animal createAnimal();
}
/* * 具體的貓類繼承抽象動物類,重寫抽象方法 */
public class Cat extends Animal {
@Override
public void eat() {
System.out.println("貓吃魚");
}
}
/* * 貓工廠類實(shí)現(xiàn)工廠類并實(shí)現(xiàn)它的抽象方法贬堵,返回一個貓對象 */
public class CatFactory implements Factory {
@Override
public Animal createAnimal() {
return new Cat();
}
}
/* * 具體的狗類繼承抽象動物類邓厕,重寫抽象方法 */
public class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃肉");
}
}
/* * 狗工廠類實(shí)現(xiàn)工廠類并實(shí)現(xiàn)它的抽象方法,返回一個狗對象 */
public class DogFactory implements Factory {
@Override
public Animal createAnimal() {
return new Dog();
}
}
/* * 測試類 */
public class AnimalDemo {
public static void main(String[] args) {
// 需求:我要買只狗
Factory f = new DogFactory();
Animal a = f.createAnimal();
a.eat();
//需求:我要買只貓
f = new CatFactory();
a = f.createAnimal();
a.eat();
}
}
運(yùn)行程序扁瓢,控制臺會輸出详恼,狗吃肉 貓吃魚
我們仔細(xì)觀察用工廠方法模式比比簡單工廠模式多了幾個類,但是當(dāng)我們在需要一種動物豬時引几,我們就不用去修改工廠類里面的代碼了昧互,只需用創(chuàng)建一個豬類繼承抽象動物類,重寫抽象方法伟桅,再創(chuàng)建一個豬的工廠類實(shí)現(xiàn)工廠類并實(shí)現(xiàn)它的抽象方法敞掘,就可以了。代碼具有很強(qiáng)的維護(hù)性和擴(kuò)展性楣铁,那么我們來分析一下工廠方法模式的優(yōu)缺點(diǎn)玖雁。
- 優(yōu)點(diǎn)
- 客戶端不需要在負(fù)責(zé)對象的創(chuàng)建,從而明確了各個類的職責(zé)盖腕,如果有新的對象增加赫冬,只需要增加一個具體的類和具體的工廠類即可浓镜,不影響已有的代碼,后期維護(hù)容易劲厌,增強(qiáng)了系統(tǒng)的擴(kuò)展性
- 缺點(diǎn)
- 需要額外的編寫代碼膛薛,增加了工作量
我們可以看到工廠方法模式的優(yōu)點(diǎn)明顯大于缺點(diǎn),所以推薦大家使用补鼻。
單例設(shè)計(jì)模式
- 單例設(shè)計(jì)模式概述
- 單例模式就是要確保類在內(nèi)存中只有一個對象哄啄,該實(shí)例必須自動創(chuàng)建,并且對外提供风范。
如何實(shí)現(xiàn)類在內(nèi)存中只有一個對象呢?
- 構(gòu)造私有
- 本身提供一個對象
- 通過公共的方法讓外界訪問
那么我們就來學(xué)習(xí)單例模式中餓漢式和懶漢式 這兩種模式咨跌,并做以比較
餓漢式
- 餓漢式:類一加載就創(chuàng)建對象
public class Student {
// 構(gòu)造私有
private Student() { }
// 自己造一個對象
// 靜態(tài)方法只能訪問靜態(tài)成員變量,加靜態(tài)
// 為了不讓外界直接訪問修改這個值硼婿,加private
private static Student s = new Student();
// 提供公共的訪問方式
// 為了保證外界能夠直接使用該方法锌半,加靜態(tài)
public static Student getStudent() { return s; }}
public class StudentDemo {
public static void main(String[] args) {
// 通過單例得到對象
Student s1 = Student.getStudent();
Student s2 = Student.getStudent();
System.out.println(s1 == s2); //true
}
}
運(yùn)行程序,控制臺會輸出true加酵,說明我們用單例模式的餓漢式確保類在內(nèi)存中只有一個對象拳喻,他的特點(diǎn)就是類一加載就創(chuàng)建對象,可以在代碼中Student類中體現(xiàn)到哭当。那么我們怎樣才能在用這個對象的時候才去創(chuàng)建它呢猪腕,我們就要來看下懶漢式了。
懶漢式
- 懶漢式:用對象的時候钦勘,才去創(chuàng)建對象
public class Teacher {
private Teacher() { }
private static Teacher t = null;
public static Teacher getTeacher() {
if (t == null) {
t = new Teacher();//當(dāng)我們?nèi)ビ眠@個對象的時候才去創(chuàng)建它
}
return t;
}
}
public class TeacherDemo {
public static void main(String[] args) {
Teacher t1 = Teacher.getTeacher();
Teacher t2 = Teacher.getTeacher();
System.out.println(t1 == t2); //true
}
}
單例模式的餓漢式和懶漢式是不是很容易理解呢陋葡,那么我們什么時候用餓漢式什么時候用懶漢式呢?
我們就來總結(jié)一下
餓漢式懶漢式比較
餓漢式我們經(jīng)常在開發(fā)中使用彻采,因?yàn)轲I漢式是不會出問題的單例模式
懶漢式我們在面試中回答用腐缤,因?yàn)閼袧h式可能會出問題的單例模式。面試主要面兩個思想肛响,分別是:
- 懶加載思想(延遲加載)
- 線程安全問題(就要考慮下面3個方面)
- 是否多線程環(huán)境
- b:是否有共享數(shù)據(jù)
- c:是否有多條語句操作共享數(shù)據(jù)
如果都是岭粤,就會存在線程的安全問題,我們上面的懶漢式代碼是不完整的特笋,應(yīng)該給對象中的方法加上synchronized關(guān)鍵字剃浇,這樣才算完整
public synchronized static Teacher getTeacher() {
if (t == null) {
t = new Teacher();
}
return t;
}
Runtime類
我們在這里為什么要說Runtime類,因?yàn)樗趈ava中的設(shè)計(jì)就是按照單例模式之餓漢式設(shè)計(jì)的猎物,我們來看一段源碼
class Runtime {
private Runtime() {}
private static Runtime currentRuntime = new Runtime();
public static Runtime getRuntime() {
return currentRuntime;
}
}
- 每個 Java 應(yīng)用程序都有一個 Runtime 類實(shí)例虎囚,使應(yīng)用程序能夠與其運(yùn)行的環(huán)境相連接∧枘ィ可以通過 getRuntime 方法獲取當(dāng)前運(yùn)行時淘讥。
- 應(yīng)用程序不能創(chuàng)建自己的 Runtime 類實(shí)例。
- Runtime類使用
- public Process exec(String command)
這個類是用來干什么的呢堤如,它可以幫助我們運(yùn)行DOS命令蒲列,比如打開記事本窒朋、計(jì)算器之類的電腦工具,當(dāng)然也有更多的功能嫉嘀,我們來體驗(yàn)一下
public class RuntimeDemo {
public static void main(String[] args) throws IOException {
Runtime r = Runtime.getRuntime();
r.exec("notepad");
}
}
運(yùn)行程序會幫我們打開記事本
r.exec("calc"); //換成calc炼邀,會幫我們打開計(jì)算機(jī)
r.exec("shutdown -s -t 1000"); // 這個命令會幫我們把電腦定時關(guān)機(jī),上面的意思就是1000秒以后關(guān)機(jī)剪侮,我們來看運(yùn)行后的效果圖
模版設(shè)計(jì)模式
- 模版設(shè)計(jì)模式概述
- 模版方法模式就是定義一個算法的骨架拭宁,而將具體的算法延遲到子類中來實(shí)現(xiàn)
- 優(yōu)點(diǎn)
- 使用模版方法模式,在定義算法骨架的同時瓣俯,可以很靈活的實(shí)現(xiàn)具體的算法杰标,滿足用戶靈活多變的需求
- 缺點(diǎn)
- 如果算法骨架有修改的話,則需要修改抽象類
我們可以在計(jì)算程序的運(yùn)行時間中應(yīng)用模版設(shè)計(jì)模式彩匕,在代碼中我們只需用改變要計(jì)算的代碼就可以了腔剂,把計(jì)算的時間設(shè)計(jì)成一個模版。
裝飾設(shè)計(jì)模式
-
裝飾設(shè)計(jì)模式概述
- 裝飾模式就是使用被裝飾類的一個子類的實(shí)例驼仪,在客戶端將這個子類的實(shí)例交給裝飾類掸犬。是繼承的替代方案
-
優(yōu)點(diǎn)
- 使用裝飾模式,可以提供比繼承更靈活的擴(kuò)展對象的功能绪爸,它可以動態(tài)的添加對象的功能湾碎,并且可以隨意的組合這些功能
-
缺點(diǎn)
- 正因?yàn)榭梢噪S意組合,所以就可能出現(xiàn)一些不合理的邏輯
在IO流中的裝飾模式應(yīng)用
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter((new OutputStreamWriter(System.out)));