一款優(yōu)秀軟件離不開一個優(yōu)秀的架構(gòu)搅荞,一個優(yōu)秀的架構(gòu)也離不開設(shè)計模式红氯,在學(xué)習(xí)設(shè)計模式之前,我們需要學(xué)習(xí)并掌握代碼設(shè)計的基本原則咕痛,打好基礎(chǔ)從設(shè)計模式的六大原則開始痢甘,六大原則如下:
- 單一職責(zé)原則
- 里氏替換原則
- 依賴倒置原則
- 接口隔離原則
- 迪米特原則
- 開閉原則
下面是對六大原則的具體介紹:
單一職責(zé)原則
單一職責(zé)原則的英文名稱是Single Responsibility Principe,縮寫是SRP茉贡。定義:就一個類而言塞栅,不要存在多于一個導(dǎo)致類變更的原因。通俗地講块仆,就是一個類中應(yīng)該是一組與其職責(zé)相關(guān)性很高的方法和數(shù)據(jù)的封裝构蹬,而不去承擔(dān)多余的職責(zé)。
例如一個用戶系統(tǒng)悔据,有姓名和年齡兩個屬性:
public class Person {
private String name;
private String age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
public void changeAge(String age) {
//...
}
public void changeName(String age) {
//...
}
}
在這個類中庄敛,name和age的set,get方法是是屬于數(shù)據(jù)類型也就是業(yè)務(wù)對象的相關(guān)方法科汗,但是changeAge和changeName需要跟服務(wù)器進行交互藻烤,是屬于業(yè)務(wù)邏輯相關(guān)方法,根據(jù)單一職責(zé)原則头滔,我們需要將業(yè)務(wù)和數(shù)據(jù)分開:
public class Person {
private String name;
private String age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
}
public class PersonLogic {
public void changeAge(String age) {
//...
}
public void changeName(String age) {
//...
}
}
優(yōu)點:可以降低類的復(fù)雜度怖亭,一個類只負責(zé)一項職責(zé);提高類的可讀性坤检,提高系統(tǒng)的可維護性兴猩;當(dāng)修改一個功能時,可以顯著降低對其他功能的影響早歇。
里氏替換原則
里氏替換原則的英文名稱是Liskov Substitution Principle倾芝,縮寫是LSP。定義:所有引用基類(父類)的地方必須能透明地使用其子類的對象箭跳。通俗地講晨另,所有使用父類的地方都可以替換為子類,不會產(chǎn)生任何錯誤和異常谱姓,但反過來替換就不行了借尿,子類出現(xiàn)的地方未必能用父類替換。
根據(jù)定義我們可以這樣理解:
- 子類必須完全實現(xiàn)父類的方法
- 子類可以有自己的個性
- 覆寫或?qū)崿F(xiàn)父類的方法時輸入?yún)?shù)可以寬于或等于父類參數(shù)
- 覆寫或?qū)崿F(xiàn)父類的方法時輸出結(jié)果可以窄于或等于父類參數(shù)
看下面參數(shù)替換的例子:
public class Father {
public void printf(ArrayList list) {
System.out.printf("父類方法");
}
public List printf() {
System.out.printf("父類方法");
return new ArrayList();
}
public void printf(HashMap map) {
System.out.printf("父類方法");
}
}
public class Son extends Father {
@Override
public void printf(ArrayList list) {
System.out.printf("子類方法--重寫父類方法");
}
@Override
public ArrayList printf() {
System.out.printf("子類方法--重寫父類方法");
// 在子類重寫父類方法中,返回值必須窄于或等于父類返回值類型
return new ArrayList();
}
// 重載方法--輸入?yún)?shù)應(yīng)該寬于或等于父類參數(shù)路翻,以免造成子類替換父類產(chǎn)生邏輯混亂
public void printf(Map map) {
System.out.printf("子類方法");
}
}
優(yōu)點:提高代碼的可擴展性狈癞。
依賴倒置原則
依賴倒置原則的英文名稱是Dependence Inversion Principle,縮寫是DIP帚桩。定義:模塊間的依賴通過抽象來產(chǎn)生亿驾,實現(xiàn)類之間不直接產(chǎn)生依賴關(guān)系,他們的依賴關(guān)系是通過接口或抽象類產(chǎn)生的账嚎。通俗地講莫瞬,就是面向接口或抽象類編程。
例如郭蕉,父親給孩子講故事疼邀,需要給他一本書,或一份報紙:
class Book{
public String get(){
return "書";
}
}
class NewsPaper{
public String get(){
return "報紙";
}
}
class Father{
public void read(Book book){
System.out.println("爸爸讀"+book.get());
}
public void read(NewsPaper news){
System.out.println("爸爸讀"+news.get());
}
}
public class Client {
public static void main(String[] args){
Father f = new Father();
f.read(new Book());
f.read(new NewsPaper());
}
}
如果再新增一個可讀的東西召锈,如Magazine(雜志)旁振,那我們要重新寫一個Magazine類,同時在Father類中還要新增一個read方法涨岁。所以我們可以這樣優(yōu)化一下:
interface IReader{
public String get();
}
然后讓Book Newspaper Magazine類都實現(xiàn)這個接口拐袜,父類則可簡化為:
class Father{
public void read(IReader reader){
System.out.println("爸爸讀"+reader.get());
}
}
優(yōu)點:提高代碼的可變性,簡化代碼邏輯梢薪。
接口隔離原則
接口隔離原則的英文名稱是Interface Segregation Principle蹬铺,縮寫是ISP昼捍。定義:一個類對另一個類的依賴應(yīng)該建立在最小的接口上污桦。通俗地講,盡量建立單一細化的接口阱持,接口中的方法盡量少琐馆,而不要建立龐大臃腫的接口规阀,也就是說我們要為各個類建立專用的接口,而不要試圖建立一個很龐大的接口供所有依賴它的類調(diào)用瘦麸,采樣接口隔離原則對接口進行約束谁撼,在一個類去實現(xiàn)接口的時候,不應(yīng)該去實現(xiàn)他不需要的方法滋饲。
常用作法:
1.為依賴接口的類提供定制服務(wù)彤敛,只暴露給類它需要的方法,不需要的方法則隱藏起來了赌。
2.提交內(nèi)聚,減少對外交互玄糟,接口方法盡量少用public修飾勿她,接口是對外的承諾,方法越少對變更產(chǎn)生的風(fēng)險越小阵翎。
interface I {
public void method1();
public void method2();
public void method3();
public void method4();
public void method5();
}
class A{
public void depend1(I i){
i.method1();
}
public void depend2(I i){
i.method2();
}
public void depend3(I i){
i.method3();
}
}
class B implements I{
public void method1() {
System.out.println("類B實現(xiàn)接口I的方法1");
}
public void method2() {
System.out.println("類B實現(xiàn)接口I的方法2");
}
public void method3() {
System.out.println("類B實現(xiàn)接口I的方法3");
}
public void method4() {}
public void method5() {}
}
class C{
public void depend1(I i){
i.method1();
}
public void depend2(I i){
i.method4();
}
public void depend3(I i){
i.method5();
}
}
class D implements I{
public void method1() {
System.out.println("類D實現(xiàn)接口I的方法1");
}
//對于類D來說逢并,method2和method3不是必需的之剧,但是由于接口A中有這兩個方法,
//所以在實現(xiàn)過程中即使這兩個方法的方法體為空砍聊,也要將這兩個沒有作用的方法進行實現(xiàn)背稼。
public void method2() {}
public void method3() {}
public void method4() {
System.out.println("類D實現(xiàn)接口I的方法4");
}
public void method5() {
System.out.println("類D實現(xiàn)接口I的方法5");
}
}
public class Client{
public static void main(String[] args){
A a = new A();
a.depend1(new B());
a.depend2(new B());
a.depend3(new B());
C c = new C();
c.depend1(new D());
c.depend2(new D());
c.depend3(new D());
}
}
可以看到,如果接口過于臃腫玻蝌,只要接口中出現(xiàn)的方法蟹肘,不管對依賴于它的類有沒有用處,實現(xiàn)類中都必須去實現(xiàn)這些方法俯树,這顯然不是好的設(shè)計帘腹。如果將這個設(shè)計修改為符合接口隔離原則,就必須對接口I進行拆分许饿。在這里我們將原有的接口I按合理的方式拆分為三個接口阳欲。
interface I1 {
public void method1();
}
interface I2 {
public void method2();
public void method3();
}
interface I3 {
public void method4();
public void method5();
}
class A{
public void depend1(I1 i){
i.method1();
}
public void depend2(I2 i){
i.method2();
}
public void depend3(I2 i){
i.method3();
}
}
class B implements I1, I2{
public void method1() {
System.out.println("類B實現(xiàn)接口I1的方法1");
}
public void method2() {
System.out.println("類B實現(xiàn)接口I2的方法2");
}
public void method3() {
System.out.println("類B實現(xiàn)接口I2的方法3");
}
}
class C{
public void depend1(I1 i){
i.method1();
}
public void depend2(I3 i){
i.method4();
}
public void depend3(I3 i){
i.method5();
}
}
class D implements I1, I3{
public void method1() {
System.out.println("類D實現(xiàn)接口I1的方法1");
}
public void method4() {
System.out.println("類D實現(xiàn)接口I3的方法4");
}
public void method5() {
System.out.println("類D實現(xiàn)接口I3的方法5");
}
}
優(yōu)點:系統(tǒng)解耦和,有利于代碼的重構(gòu)陋率、更改和重新部署球化。
迪米特原則
迪米特原則的英文全稱是Law of Demeter,縮寫是LOD瓦糟,也稱為最少知識原則(Least Knowledge Principle)筒愚。定義:一個軟件實體應(yīng)當(dāng)盡可能少地與其他實體發(fā)生相互作用。通俗地講狸页,就是盡量減少對象之間的交互锨能,如果兩個對象之間不必直接通信,那么這兩個對象就不應(yīng)該發(fā)生任何直接的相互作用芍耘。
例如址遇,一個人要租房,要求了房間的面積和租金斋竞,中介將符合我們要求的房間提供給我們就可以:
/**
* 房間
*/
public class Room {
public float area;
public float price;
public Room(float area, float price){
this.area = area;
this.price = price;
}
@Override
public String toString() {
return "Room{" + "area=" + area + ", price=" + price + '}';
}
}
/**
* 中介
*/
public class Mediator {
private List<Room> mRooms = new ArrayList<>();
public Mediator() {
for (int i = 0; i < 5; i++) {
mRooms.add(new Room(10 + i, (10 + i) * 150));
}
}
public List<Room> getAllRooms() {
return mRooms;
}
}
/**
* 租戶
*/
public class Tenant {
public float minArea = 20f;
public float maxArea = 100f;
public float minPrice = 100f;
public float maxPrice = 1000f;
public void rentRoom(Mediator mediator) {
List<Room> rooms = mediator.getAllRooms();
for (Room room : rooms) {
if (isSuitable(room)) {
System.out.print("租到房子啦:" + room);
break;
}
}
}
private boolean isSuitable(Room room) {
return room.price > minPrice && room.price < maxPrice && room.area > minArea && room.area < minArea;
}
}
從上面可以看出倔约,Tenant不僅依賴了Mediator類,還頻繁地與Room類打交道坝初,當(dāng)Room變化時Tenant也必須跟著變化浸剩,而我們只是要通過中介租房罷了,所以我們真正的“朋友”就只有Mediator鳄袍,修改后如下:
/**
* 中介
*/
public class Mediator {
private List<Room> mRooms = new ArrayList<>();
public Mediator() {
for (int i = 0; i < 5; i++) {
mRooms.add(new Room(14 + i, (14 + i) * 150));
}
}
public Room rentOut(float minArea, float maxArea, float minPrice, float maxPrice) {
for (Room room : mRooms) {
if (isSuitable(room, minArea, maxArea, minPrice, maxPrice)) {
return room;
}
}
return null;
}
private boolean isSuitable(Room room, float minArea, float maxArea, float minPrice, float maxPrice) {
return room.price > minPrice && room.price < maxPrice && room.area > minArea && room.area < minArea;
}
}
/**
* 租戶
*/
public class Tenant {
public float minArea = 20f;
public float maxArea = 100f;
public float minPrice = 500f;
public float maxPrice = 1000f;
public void rentRoom(Mediator mediator){
System.out.print("租到房子啦:"+mediator.rentOut(minArea, maxArea, minPrice, maxPrice));
}
}
優(yōu)點:降低耦合绢要,提高可擴展性。
開閉原則
開閉原則的英文名稱是Open Close Principle拗小,縮寫是OCP重罪。定義:軟件中的對象(類、模塊、函數(shù)等)對于擴展是允許的剿配,但是對于修改是不允許的搅幅。通俗地講,如果有新的需求呼胚,不能去修改實現(xiàn)類的具體實現(xiàn)茄唐,而應(yīng)該是新增創(chuàng)建一個實現(xiàn)了其公共接口的子類,在新的子類中做新需求的實現(xiàn)蝇更。
例如如下常見例子:
public interface ICar {
public String getName();
public float getPrice();
}
public class Car implements ICar{
private String name;
private float price;
public Car(String name,float price){
this.name = name;
this.price = price;
}
@Override
public String getName() {
return name;
}
@Override
public float getPrice() {
return price;
}
}
當(dāng)有一天我們獲取車的價格需要打折時沪编,可以重新寫一個類SaleCar:
public class SaleCar extends Car{
public SaleCar(String name, float price) {
super(name, price);
}
@Override
public float getPrice() {
return super.getPrice()*8/10;
}
}
優(yōu)點:讓程序更穩(wěn)定,更靈活簿寂,當(dāng)有新功能出現(xiàn)的時候漾抬,可以在不修改原有的邏輯的基礎(chǔ)上,實現(xiàn)一個新的類常遂,這樣原有的邏輯沒有變纳令,新的需求也實現(xiàn)了。當(dāng)有一天出現(xiàn)bug了克胳,可以直接修改這一個類就可以平绩。