繼承與多型(ppt 檔, 718 KB)

Download Report

Transcript 繼承與多型(ppt 檔, 718 KB)

繼承與多型
(Inheritance and Polymorphism)
鄭士康
國立台灣大學
電機工程學系/電信工程研究所/
資訊網路與多媒體研究所
1
綱要
1.
2.
3.
4.
5.
6.
7.
8.
繼承
修飾語protected
限制繼承
繼承架構下的建構函式呼叫
OCP:開放-封閉原理
多型
覆寫與隱藏
二十一點模擬程式0.1版
2
綱要
9.
10.
11.
12.
13.
14.
15.
16.
預設類別System.Object
LSP: Liskov替代性原理
抽象類別
DIP: 依存性反轉原理
介面
ISP: 介面分離原理
多重介面
*多重介面鑄形
3
綱要
1.
2.
3.
4.
5.
6.
7.
8.
繼承
修飾語protected
限制繼承
繼承架構下的建構函式呼叫
OCP:開放-封閉原理
多型
覆寫與隱藏
二十一點模擬程式0.1版
4
UsingInheritance.Calculator 片段
public int Add(int a, int b) {
int result = a + b;
return result;
}
public int Subtract(int a, int b)
{
int result = a - b;
return result;
}
public int Multiply(int a, int b)
{
int result = a * b;
return result;
}
5
UsingInheritance.
Program.Main 片段(1/2)
switch (op)
{
case ‘+’:
result = AdvancedCalculator.Add(operand1,operand2);
Console.WriteLine("{0} + {1} = {2} ",
operand1, operand2, result);
break;
. . .
6
UsingInheritance.
Program.Main
片段(2/2)
case ‘^’:
result =
AdvancedCalculator.Power(operand1,operand2);
Console.WriteLine( " {0} ^ {1} = {2}",
operand1, operand2, result);
break;
. . .
}
7
UsingInheritance.AdvancedCalculator
片段
class AdvancedCalculator : Calculator
{
public static int Power(int a, int b)
{
int result = (int)Math.Pow(a, b);
return result;
}
}
8
表示繼承的UML類別圖
9
類別繼承之階層關係
class A {
private int data1;
private int data2;
//…other members are methods
}
class B : A {
private int data3;
//…other members are methods
}
class C : B {
private int data1;
private int data4;
//…other members are methods
}
A
B
C
10
物件記憶體分配模型
A a = new A();
B b = new B();
C c = new C();
a
data1
data2
c
data1
b
data1
data2
data3
data2
data3
data1
data4
11
DataMemberInheritance.A
class A {
private int data1;
private int data2;
public A() {
data1 = 1;
data2 = 2;
}
public void GetAData(out int data1, out int data2)
{
data1 = this.data1;
data2 = this.data2;
}
}
12
DataMemberInheritance.B
class B : A {
private int data3;
public B() {
data3 = 3;
}
public void GetBData(out int data3) {
data3 = this.data3;
}
}
13
DataMemberInheritance.C
class C : B {
private int data1;
private int data4;
public C() {
data1 = 5;
data4 = 4;
}
public void GetCData(out int data1, out int data4)
{
data1 = this.data1;
data4 = this.data4;
}
}
14
DataMemberInheritance.Program.
Main 片段 (1/2)
A a = new A();
B b = new B();
C c = new C();
a.GetAData(out data1, out data2);
Debug.Assert(data1 == 1 && data2 == 2);
b.GetAData(out data1, out data2);
Debug.Assert(data1 == 1 && data2 == 2);
b.GetBData(out data3);
Debug.Assert(data3 == 3);
c.GetAData(out data1, out data2);
Debug.Assert(data1 == 1 && data2 == 2);
15
DataMemberInheritance.Program.
Main 片段 (2/2)
c.GetBData(out data3);
Debug.Assert(data3 == 3);
c.GetCData(out data1, out data4);
Debug.Assert(data1 == 5 && data4 == 4);
16
MemberFunctionInheritance.A
class A {
private int data1;
private int data2;
public A() {
data1 = 1;
data2 = 2;
}
public int GetData1() {
return data1;
}
public int GetData2() {
return data2;
}
}
17
MemberFunctionInheritance.B
class B : A {
private int data3;
public B() {
data3 = 3;
}
public int Data3 {
get { return data3; }
}
public int GetSum() {
return (GetData2() + data3);
}
}
18
MemberFunctionInheritance.C (1/2)
class C : B {
private int data1;
private int data4;
public C() {
data1 = 5;
data4 = 4;
}
public new int GetData1() {
return data1;
}
19
MemberFunctionInheritance.C (2/2)
public int GetData4() {
return data4;
}
public int GetAData1() {
return base.GetData1();
}
}
20
MemberFunctionInheritance.
Program.Main片段 (1/2)
A a = new A();
B b = new B();
C c = new C();
data1 = a.GetData1();
data2 = a.GetData2();
Debug.Assert(data1 == 1 && data2 == 2);
data1 = b.GetData1();
data2 = b.GetData2();
data3 = b.Data3;
Debug.Assert(data1 == 1 && data2 == 2 && data3 == 3);
21
MemberFunctionInheritance.
Program.Main片段 (2/2)
int sum = b.GetSum();
Debug.Assert(sum == 5);
data1 = c.GetData1();
data2 = c.GetData2();
data3 = c.Data3;
data4 = c.GetData4();
int aAData1 = c.GetAData1();
Debug.Assert(data1 == 5 && data2 == 2 &&
data3 == 3 && data4 == 4 &&
aAData1 == 1);
22
練習
• 實作並測試下列繼承關係
23
綱要
1.
2.
3.
4.
5.
6.
7.
8.
繼承
修飾語protected
限制繼承
繼承架構下的建構函式呼叫
OCP:開放-封閉原理
多型
覆寫與隱藏
二十一點模擬程式0.1版
24
CalculatorInheritance類別圖
25
CalculatorInheritance.Program.Main
片段
Calculator c = new Calculator();
c.Run();
AdvancedCalculator ac = new AdvancedCalculator();
ac.Run();
26
CalculatorInheritance.Calculator
片段
protected int register1;
protected int register2;
protected int display;
protected char op;
public Calculator() {
register1 = 0;
register2 = 0;
display = 0;
op = '+';
}
27
CalculatorInheritance.Calculator.
Run片段 (1/2)
Console.WriteLine("Calculator");
while(true) {
Console.Write("Turning off? (Y/N): ");
answer = char.Parse(Console.ReadLine());
if( answer == 'Y' || answer == 'y' ) break;
Console.Write("Enter operand 1: ");
register1 = int.Parse(Console.ReadLine());
Console.Write(
"Enter operator +, -, *, / ");
op = char.Parse(Console.ReadLine());
Console.Write("Enter operand 2: ");
register2 = int.Parse(Console.ReadLine());
28
CalculatorInheritance.Calculator.
Run片段
(2/2)
switch (op) {
case '+':
Add();
break;
case '-':
Subtract();
break;
. . . . . .
default:
Console.WriteLine(
"Should not see this message. Debug!!!");
break;
}
Console.WriteLine(display);
}
29
CalculatorInheritance.Calculator
片段
protected
display
}
protected
display
}
protected
display
}
protected
display
}
void Add() {
= register1 + register2;
void Subtract() {
= register1 - register2;
void Multiply() {
= register1 * register2;
void Divide() {
= register1 / register2;
30
CalculatorInheritance.
AdvancedCalculator (1/3)
class AdvancedCalculator : Calculator {
public new void Run() {
Console.WriteLine("Advanced Calculator");
while(true) {
Console.Write("Turning off? (Y/N): ");
answer = char.Parse(Console.ReadLine());
if( answer == 'Y' || answer == 'y' )
break;
Console.Write("Enter operand 1: ");
register1=int.Parse(Console.ReadLine());
Console.Write(
"Enter operator +, -, *, /, ^ ");
op = char.Parse(Console.ReadLine());
31
CalculatorInheritance.
AdvancedCalculator (2/3)
Console.Write("Enter operand 2:");
register2=int.Parse(Console.ReadLine());
switch (op) {
. . . . . .
case '^':
Power();
break;
default:
. . . . . .
}
Console.WriteLine(display);
}
}
32
CalculatorInheritance.
AdvancedCalculator (3/3)
protected void Power() {
display = (int) Math.Pow(register1,
register2);
}
}
33
綱要
1.
2.
3.
4.
5.
6.
7.
8.
繼承
修飾語protected
限制繼承
繼承架構下的建構函式呼叫
OCP:開放-封閉原理
多型
覆寫與隱藏
二十一點模擬程式0.1版
34
SealedClassExample.Parent
sealed class Parent {
private int data1;
public Parent() {
data1 = 0;
}
public int Data1 {
get { return data1; }
}
}
35
SealedClassExample.Child
class Child : Parent // Error!
{
private int data2;
public Child() {
data2 = 0;
}
public int Data2 {
get { return data2; }
}
}
36
綱要
1.
2.
3.
4.
5.
6.
7.
8.
繼承
修飾語protected
限制繼承
繼承架構下的建構函式呼叫
OCP:開放-封閉原理
多型
覆寫與隱藏
二十一點模擬程式0.1版
37
UsingConstructorsForInheritance.
Program.Main 片段
Animal slug = new Animal();
Animal tweety = new Animal( "canary" );
Primate godzilla = new Primate();
Primate human = new Primate( 4 );
Human jill = new Human();
38
UsingConstructorsForInheritance.
Animal
class Animal
{
private string species;
public Animal()
{
Console.WriteLine("Animal()");
species = "Animal";
}
public Animal( string s )
{
Console.WriteLine("Animal("+ s +")");
species = s;
}
}
39
UsingConstructorsForInheritanc.
Primate
class Primate : Animal
{
private int heartCham;
public Primate() : base()
{
Console.WriteLine( "Primate()" );
}
public Primate( int n ) : base( "Primate" )
{
Console.WriteLine("Primate(" + n +")");
heartCham = n;
}
}
40
UsingConstructorsForInheritanc.
Human
class Human : Primate
{
public Human() : base( 4 )
{
Console.WriteLine( "Human()" );
}
}
41
衍生物件產生流程
Primate human = new Primate( 4 );
public Primate( int n ) : base( "Primate" )
{
. . .
}
public Animal( string s )
{
. . .
}
42
練習
• 利用偵錯器體驗了解程式
UsingConstructorsForInheritance
43
綱要
1.
2.
3.
4.
5.
6.
7.
8.
繼承
修飾語protected
限制繼承
繼承架構下的建構函式呼叫
OCP:開放-封閉原理
多型
覆寫與隱藏
二十一點模擬程式0.1版
44
開放-封閉原理
(OCP:Open-Closed Principle)
• Software entities (classes, modules, functions,
etc.) should be open for extension, but closed
for modification
• 增添軟體單元新功能,但不影響此軟體單
元的其他程式碼
*Robert C. Martin, Agile Software Development: Principles, Patterns, and
Practices, Pearson Education, 2003
45
程式OCPViolationExample類別圖
46
OCPViolationExample.
Program.Main片段
Point center;
center.x = 15;
center.y = 20;
Point topLeft;
topLeft.x = 30;
topLeft.y = 40;
Shape[] list = { new Circle(2, center),
new Rectangle(3, 4, topLeft) };
DrawAllShapes(list);
47
OCPViolationExample.Program.
DrawAllShapes
static void DrawAllShapes(Shape[] list) {
for (int i = 0; i < list.Length; ++i) {
Shape s = list[i];
switch (s.type) {
case ShapeType.CIRCLE:
DrawCircle((Circle) s);
break;
case ShapeType.RECTANGLE:
DrawRectangle((Rectangle) s);
break;
}
}
}
48
OCPViolationExample.Program.
DrawCircle及DrawRectangle
static void DrawCircle(Circle c)
{
Console.WriteLine("Draw a circle");
}
static void DrawRectangle(Rectangle r)
{
Console.WriteLine("Draw a rectangle");
}
49
OCPViolationExample
class Shape {
public ShapeType type;
public Shape(ShapeType t) {
type = t;
}
}
class Circle : Shape {
private int radius;
private Point center;
public Circle(int radius, Point center)
:base(ShapeType.CIRCLE) {
this.radius = radius;
this.center = center;
}
}
50
OCPViolationExample.Rectangle
class Rectangle : Shape
{
private int width;
private int height;
private Point topLeft;
public Rectangle(int width, int height,
Point topLeft) :
base(ShapeType.RECTANGLE)
{
this.width = width;
this.height = height;
this.topLeft = topLeft;
}
}
51
OCPViolationExample的主要問題
• 添加任何有關Shape的演算(例如,拖曳、
伸縮、移動、刪除)都要重複麻煩的switch
敘述
• 增加一種新的Shape子類別,必須修改
enum敘述、各處的switch敘述,並在類
別Program增加對應的Draw函式
52
綱要
1.
2.
3.
4.
5.
6.
7.
8.
繼承
修飾語protected
限制繼承
繼承架構下的建構函式呼叫
OCP:開放-封閉原理
多型
覆寫與隱藏
二十一點模擬程式0.1版
53
物件導向的關鍵技術
• 封裝(packaging)
• 繼承(inheritance)
• 多型(polymorphism)
54
多型
• 編譯時連結(compile-time binding)與執行時連
結(run-time binding)
– 靜態連結(static binding)與動態連結(dynamic
binding)
• 多型之要求
– 繼承階層體系
– 虛擬(virtual)與覆寫(override)
– 基礎類別之參考
• 多型之優缺點
55
DrawingAllShapes類別圖
56
DrawingAllShapes.
Program.Main片段(1/2)
Shape[] list = new Shape[2];
Point center;
center.x = 15;
center.y = 20;
Point topLeft;
topLeft.x = 30;
topLeft.y = 40;
Circle c = new Circle(2, center);
Rectangle r = new Rectangle(3, 4, topLeft);
Console.WriteLine("決定畫圖順序, 輸入");
Console.WriteLine("1: 圓形, 矩形");
Console.WriteLine("2: 矩形, 圓形");
int ans = int.Parse(Console.ReadLine());
57
DrawingAllShapes.
Program.Main片段(2/2)
switch (ans)
{
case 1:
list[0]
list[1]
break;
case 2:
list[0]
list[1]
break;
default:
. . .
= c;
= r;
= r;
= c;
}
DrawAllShapes(list);
58
DrawingAllShapes.Program.
DrawAllShapes
static void DrawAllShapes(Shape[] list)
{
int i;
for (i = 0; i < list.Length; ++i)
{
list[i].Draw();
}
}
59
DrawingAllShapes.Shape
class Shape
{
public Shape() { }
virtual public void Draw() { }
}
60
DrawingAllShapes.Circle
class Circle : Shape
{
private int radius;
private Point center;
public Circle(int radius, Point center)
{
this.radius = radius;
this.center = center;
}
override public void Draw()
{
Console.WriteLine("Draw a circle");
}
}
61
DrawingAllShapes.Rectangle
class Rectangle : Shape {
private int width;
private int height;
private Point topLeft;
public Rectangle(int width, int height,
Point topLeft) {
this.width = width;
this.height = height;
this.topLeft = topLeft;
}
override public void Draw() {
Console.WriteLine("Draw a rectangle");
}
}
62
DrawingAllShapes記憶配置
堆疊記憶區
堆積記憶區
c
this
r
c.radius
c.center.x
c.center.y
Circle.Draw
Rectangle.Draw
this
r.width
r.height
r.topLeft.x
r.topLeft.y
63
練習
• 在程式DrawingAllShapes增加類別
Triangle,使程式也能畫出三角形。
64
綱要
1.
2.
3.
4.
5.
6.
7.
8.
繼承
修飾語protected
限制繼承
繼承架構下的建構函式呼叫
OCP:開放-封閉原理
多型
覆寫與隱藏
二十一點模擬程式0.1版
65
NewVsOverride.Program類別圖
66
NewVsOverride.Car
// Define the base class
class Car
{
public virtual void DescribeCar()
{
System.Console.WriteLine(
"Four wheels and an engine.");
}
}
67
NewVsOverride.ConvertibleCar
// Define the derived classes
class ConvertibleCar : Car
{
public new virtual void DescribeCar()
{
base.DescribeCar();
Console.WriteLine(
"A roof that opens up.");
}
}
68
NewVsOverride.Minivan
class Minivan : Car
{
public override void DescribeCar()
{
base.DescribeCar();
Console.WriteLine(
"Carries seven people.");
}
}
69
NewVsOverride.Program.Main
片段 (1/2)
// new and override make no differences here
Car car1 = new Car();
car1.DescribeCar();
Console.WriteLine("----------");
ConvertibleCar car2 = new ConvertibleCar();
car2.DescribeCar();
Console.WriteLine("----------");
Minivan car3 = new Minivan();
car3.DescribeCar();
Console.WriteLine("----------");
70
NewVsOverride.Program.Main
片段 (2/2)
// they are different in polymorphysm
Car[] cars = new Car[3];
cars[0] = new Car();
cars[1] = new ConvertibleCar();
cars[2] = new Minivan();
foreach (Car vehicle in cars)
{
Console.WriteLine("Car object: " +
vehicle.GetType());
vehicle.DescribeCar();
Console.WriteLine("----------");
}
71
覆寫與隱藏
• 覆寫: override
– 主要用於Polymorphism (多型)
– 執行時連結
• 隱藏: new
– 只是取代基底類別同名之成員變數與方法
– 仍是編譯時連結
72
練習
• 利用偵錯器體驗了解程式NewVsOverride
73
綱要
1.
2.
3.
4.
5.
6.
7.
8.
繼承
修飾語protected
限制繼承
繼承架構下的建構函式呼叫
OCP:開放-封閉原理
多型
覆寫與隱藏
二十一點模擬程式0.1版
74
BlackJack_0_1 類別圖
75
BlackJack_0_1.BlackJackTest
片段(1/2)
public static bool Scenario1_OK()
{
Card[] cards = { new Card(Suit.SPADE, 1),
new Card(Suit.HEART, 11),
new Card(Suit.DIAMOND, 10)
};
Deck deck = new Deck(cards);
Player player = new Player();
Dealer dealer = new Dealer();
76
BlackJack_0_1.BlackJackTest
片段(2/2)
player.SaveACard(deck.DealACard());
dealer.SaveACard(deck.DealACard());
player.SaveACard(deck.DealACard());
return(
player.GetStatus() == Status.BLACK_JACK
&& dealer.GetStatus() == Status.PASS);
}
77
BlackJack_0_1.Game 片段 (1/6)
const int N_PLAYERS = 2;
Deck deck;
Player[] players = new Player[N_PLAYERS];
public Game()
{
players[0] = new Player("Jeng");
players[N_PLAYERS-1] = new Dealer();
}
78
BlackJack_0_1.Game 片段 (2/6)
private void Play()
{
int i;
// 第一輪發牌
for (i = 0; i < N_PLAYERS; ++i)
{
players[i].SaveACard(
deck.DealACard());
players[i].Dump();
}
79
BlackJack_0_1.Game 片段 (3/6)
// 第二輪發牌
for (i=0; i < N_PLAYERS; ++i)
{
players[i].SaveACard(
deck.DealACard());
players[i].Dump();
}
80
BlackJack_0_1.Game 片段 (4/6)
// 開始要牌計牌
for(i=0; i<N_PLAYERS; ++i)
{
while (players[i].GetStatus() ==
Status.PASS &&
players[i].WantOneMoreCard() &&
deck.HasMoreCard())
{
players[i].SaveACard(deck.DealACard());
players[i].Dump();
if(IsBlackJackOrBurst(players[i]))
return;
}
}
81
BlackJack_0_1.Game 片段 (5/6)
// 計點分勝負
Player dealer = players[N_PLAYERS-1];
for(i=0; i<N_PLAYERS-1; ++i) {
if (dealer.GetTotalPoints() >=
players[i].GetTotalPoints()) {
Console.WriteLine( dealer.Name +
"勝"+players[i].Name);
} else {
Console.WriteLine(
players[i].Name+"勝"+dealer.Name);
}
}
}
82
BlackJack_0_1.Game 片段 (6/6)
private bool IsBlackJackOrBurst(
Player player) {
bool isBlackJack = false;
if (player.GetStatus()==Status.BLACK_JACK) {
isBlackJack = true;
Console.WriteLine(player.Name+
" BlackJack!!!");
}
bool isBurst = false;
if (player.GetStatus() == Status.BURST){
isBurst = true;
Console.WriteLine(player.Name+" 爆!!!");
}
return (isBlackJack || isBurst);
}
83
BlackJack_0_1.Player 片段(1/5)
private Card[] hand = new Card[11];
private int nCards;
private Status status;
private int totalPoints;
private string name;
public Player()
{
nCards = 0;
name = "無名氏";
}
84
BlackJack_0_1.Player 片段(2/5)
public Player(string name)
{
nCards = 0;
this.name = name;
}
public string Name
{
get { return name; }
}
85
BlackJack_0_1.Player 片段(3/5)
virtual public bool WantOneMoreCard()
{
Console.Write("要再一張牌嗎? (y/n) ");
string answer = Console.ReadLine();
return (answer == "Y" || answer == "y");
}
86
BlackJack_0_1.Player 片段(4/5)
public void Dump()
{
int i;
Console.Write(name+" 牌: ");
for (i = 0; i < nCards; ++i)
{
hand[i].Dump();
Console.Write("\t");
if ((i + 1) % 5 == 0)
Console.WriteLine();
}
87
BlackJack_0_1.Player 片段(5/5)
Console.WriteLine();
Console.WriteLine(name + " 總點數: " +
totalPoints);
}
88
BlackJack_0_1.Dealer片段
class Dealer : Player {
public Dealer() : base("莊家") {}
override public bool WantOneMoreCard()
{
return (base.GetTotalPoints() < 17);
}
}
89
綱要
9.
10.
11.
12.
13.
14.
15.
16.
預設類別System.Object
LSP: Liskov替代性原理
抽象類別
DIP: 依存性反轉原理
介面
ISP: 介面分離原理
多重介面
*多重介面鑄形
90
預設類別System.Object
• 一致化型別
• 成員函式
– Equals
– GetHashCode
– GetType
– ReferenceEquals
– ToString
– Finalize
91
InheritingObject.Program.Main
片段
Test t1 = new Test();
Test t2 = new Test();
bool isEqual = t1.Equals(t2);
Console.WriteLine(t1.ToString());
Console.WriteLine("t1 與t2 相等為" + isEqual);
92
InheritingObject.Test
class Test
{
override public string ToString()
{
return "覆寫InheritingObject.Test";
}
}
93
Boxing 與 Unboxing
int x = 10;
Object obj = (Object) x; // boxing
obj = 20;
int j = (int)obj; // unboxing
94
綱要
9.
10.
11.
12.
13.
14.
15.
16.
預設類別System.Object
LSP: Liskov替代性原理
抽象類別
DIP: 依存性反轉原理
介面
ISP: 介面分離原理
多重介面
*多重介面鑄形
95
Liskov 代替性原理
(LSP: Liskov Substitution Principle)
• Subtype must be substitutable for their base
types
• 良好的繼承設計
– 對形態S之每一物件o1,有形態T的物件o2,使在
所有利用型態T物件的程式P中,P的行為不因
以o1代替o2而改變
• 破壞LSP常也破壞OCP
*Robert C. Martin, Agile Software Development: Principles, Patterns, and
Practices, Pearson Education, 2003
96
IS-A 關係與繼承
97
LSPViolationExample.Rectangle
(1/2)
class Rectangle
{
private int width;
private int height;
virtual public int Width
{
set { width = value; }
}
virtual public int Height
{
set { height = value; }
}
98
LSPViolationExample.Rectangle
(2/2)
public int Area()
{
return width * height;
}
}
99
LSPViolationExample.Square
class Square : Rectangle
{
override public int Width
{
set { base.Width = value;
base.Height = value; }
}
override public int Height
{
set { base.Width = value;
base.Height = value; }
}
}
100
LSPViolationExample.Program
class Program
{
static void Main(string[] args)
{
Square s = new Square();
Test(s);
}
static void Test(Rectangle r)
{
r.Width = 5;
r.Height = 4;
Debug.Assert(r.Area() == 20);
}
}
101
函式的進入與離開條件
• 進入與離開函式時的假設
• Rectangle.Width的離開條件
Debug.Assert( (width == value) &&
(height == old.height));
• 符合LSP之子類別函式覆寫的要求
– 進入條件需等於父類別被覆寫函式之進入條件,
或更寬鬆
– 離開條件需等於父類別被覆寫函式之離開條件,
或更嚴格
102
綱要
9.
10.
11.
12.
13.
14.
15.
16.
預設類別System.Object
LSP: Liskov替代性原理
抽象類別
DIP: 依存性反轉原理
介面
ISP: 介面分離原理
多重介面
*多重介面鑄形
103
抽象類別
104
AbstractClassExample.
Program.Main() 片段
double a = 5.0;
Square sq = new Square(a);
Console.WriteLine("正方形sq之面積為" + sq.Area());
Circle c = new Circle(a);
Console.WriteLine("圓形c之面積為" + c.Area());
105
AbstractClassExample.Shape
public abstract class Shape {
private string shape;
public Shape(string shape) {
this.shape = shape;
Console.WriteLine("建立" + shape);
}
abstract public double Area();
}
106
AbstractClassExample.Square
public class Square : Shape
{
double a;
public Square(double a): base("正方形")
{
this.a = a;
}
public override double Area()
{
return a * a;
}
}
107
AbstractClassExample.Circle
public class Circle : Shape
{
double r;
public Circle(double r): base("圓形")
{
this.r = r;
}
public override double Area()
{
return Math.PI * r * r;
}
}
108
練習
• 將程式DrawingAllShapes中的類別
Shape改為抽象類別,並將Shape.Draw()
改為抽象函式
109
抽象類別
•
•
•
•
修飾語
欄位變數
建構式
函式方法覆寫與實作
110
綱要
9.
10.
11.
12.
13.
14.
15.
16.
預設類別System.Object
LSP: Liskov替代性原理
抽象類別
DIP: 依存性反轉原理
介面
ISP: 介面分離原理
多重介面
*多重介面鑄形
111
依存性反轉原理
(DIP: Dependency-Inversion Principle)
• High-level modules should not depend on lowlevel modules. Both should depend on
abstractions.
• Abstractions should not depend on details.
Details should depend on abstractions.
*Robert C. Martin, Agile Software Development: Principles, Patterns, and
Practices, Pearson Education, 2003
112
一個違反DIP的例子
113
狀態圖(State Chart)
114
DIPViolationExample.
LampStatus及ButtonStatus
public enum LampStatus
{
OFF = 0,
ON = 1
}
public enum ButtonStatus
{
RELEASED = 0,
PRESSED = 1
}
115
DIPViolationExample.Lamp
public class Lamp {
private LampStatus status = LampStatus.OFF;
public LampStatus Status {
get { return status; }
}
public void TurnOn() {
status = LampStatus.ON;
}
public void TurnOff() {
status = LampStatus.OFF;
}
}
116
DIPViolationExample.Button
(1/2)
public class Button
{
private ButtonStatus status =
ButtonStatus.RELEASED;
private Lamp lamp;
public Button(Lamp lamp)
{
this.lamp = lamp;
}
public ButtonStatus Status {
get { return status; }
}
117
DIPViolationExample.Button
(2/2)
public void Press() {
if (status == ButtonStatus.RELEASED) {
status = ButtonStatus.PRESSED;
lamp.TurnOn();
}
}
public void Release() {
if( status == ButtonStatus.PRESSED ){
status = ButtonStatus.RELEASED;
lamp.TurnOff();
}
}
}
118
DIPViolationExample.
Program.Main() 片段 (1/3)
Lamp lamp1 = new Lamp(1);
Button button = new Button(lamp1);
Random rand = new Random();
for (int n = 0; n <= 100; ++n)
{
Console.Write("time n = " + n + "\t");
119
DIPViolationExample.
Program.Main() 片段 (2/3)
if (rand.Next() % 2 == 1)
{
if (button.Status ==
ButtonStatus.PRESSED)
{
button.Release();
}
else
{
button.Press();
}
}
120
DIPViolationExample.
Program.Main() 片段 (3/3)
if (lamp1.Status == LampStatus.OFF)
{
Console.WriteLine("lamp1 is off");
}
else
{
Console.WriteLine("lamp1 is on");
}
Console.WriteLine();
}
121
綱要
9.
10.
11.
12.
13.
14.
15.
16.
預設類別System.Object
LSP: Liskov替代性原理
抽象類別
DIP: 依存性反轉原理
介面
ISP: 介面分離原理
多重介面
*多重介面鑄形
122
介面
123
UsingInterface.
Program.Main片段
double a = 5.0;
Square sq = new Square(a);
Console.WriteLine("正方形sq之面積為" + sq.Area());
Circle c = new Circle(a);
Console.WriteLine("圓形c之面積為" + c.Area());
124
UsingInterface.Shape
interface Shape
{
double Area();
}
125
UsingInterface.Square
public class Square : Shape
{
double a;
public Square(double a)
{
this.a = a;
}
public double Area()
{
return a * a;
}
}
126
UsingInterface.Circle
public class Circle : Shape
{
double r;
public Circle(double r)
{
this.r = r;
}
public double Area()
{
return Math.PI * r * r;
}
}
127
介面 vs. 抽象類別 (1/2)
interface Shape
{
double Area();
}
-------------------------------------------public abstract class Shape {
private string shape;
public Shape(string shape) {
this.shape = shape;
Console.WriteLine("建立" + shape);
}
abstract public double Area();
}
128
介面 vs. 抽象類別 (2/2)
public class Square : Shape {
. . .
public double Area() {
return a * a;
}
}
-------------------------------------------public class Square : Shape {
. . .
public override double Area() {
return a * a;
}
}
129
一個符合DIP的設計
130
ButtonAndLamp
public interface SwitchableDevice {
void TurnOn();
void TurnOff();
}
public enum LampStatus {
OFF = 0,
ON = 1
}
public enum ButtonStatus {
RELEASED = 0,
PRESSED = 1
}
131
ButtonAndLamp.Lamp
public class Lamp : SwitchableDevice
{
private LampStatus status = LampStatus.OFF;
public LampStatus Status {
get { return status; }
}
public void TurnOn() {
status = LampStatus.ON;
}
public void TurnOff() {
status = LampStatus.OFF;
}
}
132
ButtonAndLamp.Button (1/2)
public class Button
{
private ButtonStatus status =
ButtonStatus.RELEASED;
private SwitchableDevice device;
public Button(SwitchableDevice device)
{
this.device = device;
}
public ButtonStatus Status {
get { return status; }
}
133
ButtonAndLamp.Button (2/2)
public void Press() {
if (status == ButtonStatus.RELEASED) {
status = ButtonStatus.PRESSED;
device.TurnOn();
}
}
public void Release() {
if (status == ButtonStatus.PRESSED) {
status = ButtonStatus.RELEASED;
device.TurnOff();
}
}
}
134
練習
• 在程式ButtonAndLamp增加類別Fan,使
Button也可以控制Fan的開與關
135
綱要
9.
10.
11.
12.
13.
14.
15.
16.
預設類別System.Object
LSP: Liskov替代性原理
抽象類別
DIP: 依存性反轉原理
介面
ISP: 介面分離原理
多重介面
*多重介面鑄形
136
介面分離原理
(ISP: Interface-Segregation Principle)
• Clients should not be forced to depend on
methods that they do not use
• 避免同一介面中有目的不同的多群函式宣
告
137
一個違反ISP的例子
138
ISPViolationExample
interface TimerClient {
void TimeOut();
}
interface Door : TimerClient {
void Lock();
void Unlock();
bool IsOpen();
}
enum DoorStatus {
CLOSED = 0,
OPEN = 1
}
139
ISPViolationExample.Timer (1/2)
class Timer
{
private int t;
private int timeout;
private TimerClient client;
public Timer(int timeout,
TimerClient client)
{
this.timeout = timeout;
this.client = client;
t = 0;
}
140
ISPViolationExample.Timer (2/2)
public void Advance() {
++t;
if (t % timeout == 0) {
client.TimeOut();
}
}
}
141
ISPViolationExample.TimedDoor
(1/2)
class TimedDoor : Door
{
private DoorStatus status =
DoorStatus.CLOSED;
public bool IsOpen() {
return (status == DoorStatus.OPEN);
}
public void Lock() {
if (IsOpen()) status = DoorStatus.CLOSED;
}
public void Unlock() {
if (!IsOpen()) status = DoorStatus.OPEN;
}
142
ISPViolationExample.TimedDoor
(2/2)
public void TimeOut() {
Lock();
}
}
143
ISPViolationExample.
Program.Main 片段(1/2)
TimedDoor tDoor = new TimedDoor();
int timeout = 10;
Timer timer = new Timer(timeout, tDoor);
int n;
const int N = 20;
tDoor.Unlock();
for (n = 0; n <= N; ++n)
{
144
ISPViolationExample.
Program.Main片段(2/2)
if (tDoor.IsOpen())
{
Console.WriteLine(
"n = " + n + "\t tDoor is open");
}
else
{
Console.WriteLine(
"n = " + n + "\t tDoor is closed");
}
timer.Advance();
}
145
綱要
9.
10.
11.
12.
13.
14.
15.
16.
預設類別System.Object
LSP: Liskov替代性原理
抽象類別
DIP: 依存性反轉原理
介面
ISP: 介面分離原理
多重介面
*多重介面鑄形
146
程式MultiInterface類別圖
147
MultiInterface.Program.Main
片段
Floatplane fp = new Floatplane();
fp.Sail();
fp.Fly();
148
MultiInterface
interface Plane
{
void Fly();
}
interface Ship
{
void Sail();
}
149
MultiInterface.Floatplane
public class Floatplane : Plane, Ship {
public Floatplane() {
Console.WriteLine("建立水上飛機");
}
public void Sail() {
Console.WriteLine("水上滑行");
}
public void Fly() {
Console.WriteLine("空中飛行");
}
}
150
練習
• 利用多重介面, 設計並測試一個類別
Clock_Radio,兼具介面Clock之
GetTime()與介面Radio之PlayMusic()
功能
151
一個符合ISP的設計
152
TimedDoorSimulation片段
interface TimerClient {
void TimeOut();
}
interface Door {
void Lock();
void Unlock();
bool IsOpen();
}
. . .
class TimedDoor : Door, TimerClient
{
. . .
}
153
綱要
9.
10.
11.
12.
13.
14.
15.
16.
預設類別System.Object
LSP: Liskov替代性原理
抽象類別
DIP: 依存性反轉原理
介面
ISP: 介面分離原理
多重介面
*多重介面鑄形
154
程式CastMultiInterfaces類別圖
155
CastMultiInterfaces.
Program.Main片段
double a = 5.0;
Square sq = new Square(a);
Rhombus rhomb = sq as Rhombus;
Console.WriteLine(
"sq的面積以菱形公式計算得"+rhomb.Area() );
if( sq is Rectangle )
{
Rectangle rec = (Rectangle) sq;
Console.WriteLine(
"sq的面積以矩形公式計算得"+rec.Area() );
}
156
CastMultiInterfaces
interface Rectangle
{
double Area();
}
interface Rhombus
{
double Area();
}
157
CastMultiInterfaces.Square
(1/2)
public class Square : Rectangle, Rhombus
{
private double a;
private double d;
public Square(double a)
{
this.a = a;
d = Math.Sqrt(2.0) * a;
}
158
CastMultiInterfaces.Square
(2/2)
double Rectangle.Area()
{
return a * a;
}
double Rhombus.Area()
{
return 0.5 * d * d;
}
}
159