一 引子git
都說面向對象的4大支柱是抽象,封裝,繼承與多態。可是一些初涉編程的開發人員,體會不到繼承與多態的妙用,本文就試以一個經典實例來詮釋繼承與多態的用武之地。本實例的需求來自《重構》一書。github
二 需求
編程
咱們的需求是一個影片出租的小應用,該應用會記錄每一個顧客的消費金額並打印出來。
程序輸入爲:顧客租的影片及對應的租期;
程序的處理爲:根據顧客租用影片時間及影片類型,計算費用;輸出:打印消費單。
影片有三種類型:普通影片、兒童影片及新上映影片。
另外,模仿時下潮流,程序還提供了積分制度,爲常客計算積分 ,積分會根據影片是否爲新上映影片而不一樣。ide
租賃費用計算:函數
影片類型爲兒童片,兩天之內費用爲2,超出兩天的時間,天天的費用爲1.5this
影片類型爲新片,天天的費用爲3url
影片類型爲普通片,三天之內費用爲1.5,超出三天,天天的費用爲1.5spa
積分計算:3d
每次租賃影片,積分加一,若是影片爲新片且租賃時間大於1天,則多加一分
2. 本實例爲控制檯程序,運行界面以下:
三 非繼承多態實現方式
根據需求,咱們定義三個類,分別是movie類,Rental類(表明一條租用記錄)和Customer類(租碟顧客)
其中movie類的代碼以下:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 6 namespace _1.cntbed 7 { 8 public enum TYPE 9 { 10 REGULAR, 11 NEW_RELEASE, 12 CHILDRENS 13 } 14 15 class Movie 16 { 17 private string _title; //movie name 18 TYPE _typeCode; //price code 19 20 public Movie() 21 { 22 _title = "unname"; 23 _typeCode = 0; 24 } 25 26 public Movie(string title, TYPE typeCode) 27 { 28 _title = title; 29 _typeCode = typeCode; 30 } 31 32 public TYPE getTypeCode() 33 { 34 return (TYPE)_typeCode; 35 } 36 37 void setTypeCode(TYPE arg) 38 { 39 _typeCode = arg; 40 } 41 42 public string getTitle() 43 { 44 return _title; 45 } 46 } 47 }
Rental類的代碼以下,租用記錄中包含了一個movie對象,以及一個租期成員(積分和租金與此有關):
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace _1.cntbed { class Rental { private Movie _movie; int _daysRented; public Rental(Movie movie, int daysRented) { _movie = movie; _daysRented = daysRented; } public int getDaysRented() { return _daysRented; } public Movie getMovie() { return _movie; } } }
Customer類的代碼以下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace _1.cntbed { class Customer { private string _name; List<Rental> _rentals = new List<Rental>(); public Customer(string name) { _name = name; } public void addRental(Rental arg) { _rentals.Add(arg); } string getName() { return _name; } public string statement() { double totalAmount = 0; //總共的租金 int frequentRenterPoints = 0;//積分 string result = "\r-------------------------------------------\n\r " + "租碟記錄--- " + getName() + "\n"; foreach (Rental iter in _rentals) { double thisAmount = 0; Rental each = iter; switch (each.getMovie().getTypeCode()) { case TYPE.REGULAR: thisAmount += 2;//2天以內2元 if (each.getDaysRented() > 2) thisAmount += (each.getDaysRented() - 2) * 1.5; break; case TYPE.NEW_RELEASE: thisAmount += each.getDaysRented() * 3; break; case TYPE.CHILDRENS: thisAmount += 1.5;//3天以內1.5元 if (each.getDaysRented() > 3) thisAmount += (each.getDaysRented() - 3) * 1.5; break; } frequentRenterPoints++;//對於每一種類型的影片,一次租用積分加1 if ((each.getMovie().getTypeCode() == TYPE.NEW_RELEASE) && each.getDaysRented() > 1) frequentRenterPoints++; result += "\n\t" + each.getMovie().getTitle() + "\t" + thisAmount; totalAmount += thisAmount; } result += "\n共消費 " + totalAmount + " 元" + "\n您增長了 " + frequentRenterPoints + " 個積分\n"; return result; } } }
主程序代碼以下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace _1.cntbed { class Program { static void Main(string[] args) { Console.Write("影碟店客戶租碟明細"); Movie m1 = new Movie("致咱們終將逝去的青春", TYPE.NEW_RELEASE); Movie m2 = new Movie("我是特種兵之利刃出鞘", TYPE.REGULAR); Movie m3 = new Movie("熊出沒之環球大冒險", TYPE.CHILDRENS); Rental r1 = new Rental(m1, 4); Rental r2 = new Rental(m1, 2); Rental r3 = new Rental(m3, 7); Rental r4 = new Rental(m2, 5); Rental r5 = new Rental(m3, 3); Customer c1 = new Customer("孫紅雷"); c1.addRental(r1); c1.addRental(r4); Customer c2 = new Customer("林志玲"); c2.addRental(r1); c2.addRental(r3); c2.addRental(r2); Customer c3 = new Customer("劉德華"); c3.addRental(r3); c3.addRental(r5); Customer c4 = new Customer("孫儷"); c4.addRental(r2); c4.addRental(r3); c4.addRental(r5); Console.Write(c1.statement()); Console.Write(c2.statement()); Console.Write(c3.statement()); Console.Write(c4.statement()); } } }
四 繼承多態實現方式
咱們定義一個Movie父類,每種影片類型均定義一個Movie子類(ChildrensMovie,NewReleaseMovie,RegularMovie),同時定義一個Movie工廠類(MovieFactoryMethod)。代碼以下:
新的Movie類的代碼以下:Movie類提供積分計算和租金計算的默認實現。
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace _1.cntbed { public enum TYPE { REGULAR, NEW_RELEASE, CHILDRENS } public class Movie { protected string _title; //movie name TYPE _priceCode; //price code public Movie() { _title = "unname"; _priceCode = 0; } public Movie(string title, TYPE priceCode) { _title = title; _priceCode = priceCode; } public virtual double getCharge(int daysRented) { return 0;//收費 } public virtual int getFrequentRenterPoints(int daysRented)//積分 { return 1; } public TYPE getPriceCode() { return (TYPE)_priceCode; } void setPriceCode(TYPE arg) { _priceCode = arg; } public string getTitle() { return _title; } } }
ChildrensMovie子類的代碼以下:重寫租金計算方法(多態性),積分計算方法從父類繼承。
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace _1.cntbed { public class ChildrensMovie : Movie { public ChildrensMovie (string title) { _title = title; } public override double getCharge(int daysRented) { double result = 1.5; if (daysRented > 3) result += (daysRented - 3) * 1.5; return result; } } }
NewReleaseMovie子類的代碼以下:重寫租金計算方法(多態性)和積分計算方法(多態性)。
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace _1.cntbed { public class NewReleaseMovie:Movie { public NewReleaseMovie (string title) { _title = title; } public override double getCharge(int daysRented) { return daysRented * 3; } public override int getFrequentRenterPoints(int daysRented) { return (daysRented > 1) ? 2 : 1; } } }
RegularMovie子類的代碼以下:重寫租金計算方法(多態性),積分計算方法從父類繼承。
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace _1.cntbed { public class RegularMovie : Movie { public RegularMovie(string title) { _title = title; } public override double getCharge(int daysRented) { double result = 2; if (daysRented > 2) result += (daysRented - 2) * 1.5; return result; } } }
Rental類的代碼保持不變:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace _1.cntbed { class Rental { private Movie _movie; int _daysRented; public Rental(Movie movie, int daysRented) { _movie = movie; _daysRented = daysRented; } public int getDaysRented() { return _daysRented; } public Movie getMovie() { return _movie; } } }
MovieFactoryMethod類的代碼以下:根據不一樣的影片類型,返回相應的派生類對象,注意這裏的函數的返回值是父類。
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace _1.cntbed { public class MovieFactoryMethod { public Movie MakeMovie(string strTitle, TYPE arg) { switch (arg) { case TYPE.REGULAR: return new RegularMovie(strTitle); case TYPE.CHILDRENS: return new ChildrensMovie(strTitle); case TYPE.NEW_RELEASE: return new NewReleaseMovie(strTitle); default: //cout << "Incorrect Price Code" << endl; return null; } } } }
新的Customer代碼以下:變得簡單了,不用關心List裏的Movie類對象的真正類型,會自動調用相應派生類的方法
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace _1.cntbed { class Customer { private string _name; List<Rental> _rentals = new List<Rental>(); public Customer(string name) { _name = name; } public void addRental(Rental arg) { _rentals.Add(arg); } string getName() { return _name; } public string statement() { double totalAmount = 0; //總共的租金 int frequentRenterPoints = 0;//積分 string result = "\r-------------------------------------------\n\r " + "租碟記錄--- " + getName() + "\n"; foreach (Rental iter in _rentals) { Rental each = iter; frequentRenterPoints += each.getMovie().getFrequentRenterPoints(each.getDaysRented()); result += "\n\t" + each.getMovie().getTitle() + "\t" + each.getMovie().getCharge(each.getDaysRented()); totalAmount += each.getMovie().getCharge(each.getDaysRented()); } result += "\n共消費 " + totalAmount + " 元" + "\n您增長了 " + frequentRenterPoints + " 個積分\n"; return result; } } }
最後,主程序的代碼以下:注意Rental類接受一個Movie類的參數,可是咱們能夠傳遞給他一個派生類對象。
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace _1.cntbed { class Program { static void Main(string[] args) { Console.Write("影碟店客戶租碟明細"); MovieFactoryMethod mfm = new MovieFactoryMethod(); Movie m1 = mfm.MakeMovie("致咱們終將逝去的青春", TYPE.NEW_RELEASE); Movie m2 = mfm.MakeMovie("我是特種兵之利刃出鞘", TYPE.REGULAR); Movie m3 = mfm.MakeMovie("熊出沒之環球大冒險", TYPE.CHILDRENS); Rental r1 = new Rental(m1, 4); Rental r2 = new Rental(m1, 2); Rental r3 = new Rental(m3, 7); Rental r4 = new Rental(m2, 5); Rental r5 = new Rental(m3, 3); Customer c1 = new Customer("孫紅雷"); c1.addRental(r1); c1.addRental(r4); Customer c2 = new Customer("林志玲"); c2.addRental(r1); c2.addRental(r3); c2.addRental(r2); Customer c3 = new Customer("劉德華"); c3.addRental(r3); c3.addRental(r5); Customer c4 = new Customer("孫儷"); c4.addRental(r2); c4.addRental(r3); c4.addRental(r5); Console.Write(c1.statement()); Console.Write(c2.statement()); Console.Write(c3.statement()); Console.Write(c4.statement()); } } }
五 總結
函數的返回值是父類,咱們卻能夠返回一個派生類對象;函數的參數是父類,咱們卻能夠傳入一個派生類對象。foreach循環遍歷List<>,程序會自動根據List裏保存的對象的真正類型,引用相應的方法。
咱們這個需求,不管租何種影片,租多長時間,都送一積分,故在Movie基類提供了積分計算方法getFrequentRenterPoints()的默認實現;NewReleaseMovie類型的影片積分計算方法有所不一樣,故重寫了getFrequentRenterPoints()方法;關於租金計算方法getCharge,你們能夠試着自行分析。
考慮增長支持一種新影片類型-TVB電視劇:積分和租金的計算規則以下:
租金計算方式:借7天以內收5元,超過7天,以後天天收2元
積分:只要租了就積1分,而後每達到3天的倍數積1分(好比1-2天積1分,3-5天積2分)
要求在以上2個小框架中,分別實現該需求,而後,回過頭來看看,那種方式更加方便,就能夠更好的體會到繼承和多態的強大之處了。
這裏先揭示一下,在非繼承多態框架下,實現新增一種影片類型或現有影片類型的積分或租金計算規則改變了,Customer都須要進行改動;而在繼承多態框架下,實現新增一種影片類型或現有影片類型的積分或租金計算規則改變了,Customer無需任何改動。在繼承多態框架下,若要新增一種影片類型,則只需新增一個Movie派生類;現有影片類型的積分或租金計算規則改變了,則只需重寫相應派生類型的積分或租金計算函數。
最後說一句,大型項目中,Customer的維護者和Movie家族類的維護者有可能不是同一我的,這樣,需求的變動對於Customer的維護者來講是透明的,Customer的維護者可能都不知道什麼時候增長了幾種影片類型。
六 源碼下載