Abstract V.S. Virtual

Abstract:

  • An abstract class/method MUST be implemented by derived classes
  • Abstract classes cannot be instantiated directly
  • Classes with abstract methods must be declared abstract
  • Abstract methods have no implementation in the base class

Virtual:

  • A virtual method CAN be overridden by derived classes
  • Virtual methods have a default implementation in the base class
  • Classes with virtual methods can be instantiated
  • Derived classes may choose to override or use the base implementation

SOLID

  1. Single responsibility principle(SRP) 單一職責
    「單一職責」原則顧名思義,就是一個類別應該只負責一個職責。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public void CreateOrder(){
ReceiveOrder(); // 職責1: 接收訂單
WriteInOrder(); // 職責2: 寫入訂單
GetInformation(); // 職責3: 獲取資訊
SendNotification(); // 職責4: 發送通知
}
public class OrderReceiver {
public void ReceiveOrder() {
// 接收訂單邏輯
}
}

// 職責2: 只負責寫入訂單
public class OrderWriter {
public void WriteOrder() {
// 寫入訂單邏輯
}
}

// 職責3: 只負責獲取資訊
public class InformationGetter {
public void GetInformation() {
// 獲取資訊邏輯
}
}

// 職責4: 只負責發送通知
public class NotificationSender {
public void SendNotification() {
// 發送通知邏輯
}
}
  1. Open/close principle(OCP) 開放/封閉原則
    軟體實體(類別、模組、函式等等)應該對擴展開放,而對修改封閉
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
// 抽象形狀類別
public abstract class Shape
{
public abstract double CalculateArea();
}

// 具體實現:矩形
public class Rectangle : Shape
{
private double width;
private double height;

public Rectangle(double width, double height)
{
this.width = width;
this.height = height;
}

public override double CalculateArea()
{
return width * height;
}
}
// 具體實現:圓形
public class Circle : Shape
{
private double radius;

public Circle(double radius)
{
this.radius = radius;
}

public override double CalculateArea()
{
return Math.PI * radius * radius;
}
}

// 具體實現:三角形
public class Triangle : Shape
{
private double baseLength;
private double height;

public Triangle(double baseLength, double height)
{
this.baseLength = baseLength;
this.height = height;
}

public override double CalculateArea()
{
return 0.5 * baseLength * height;
}
}

public class AreaCalculator
{
public double Calculate(Shape shape)
{
return shape.CalculateArea();
}
}

var calculator = new AreaCalculator();

var rectangle = new Rectangle(10, 5);
var circle = new Circle(7);
var triangle = new Triangle(6, 4);

Console.WriteLine($"Rectangle Area: {calculator.Calculate(rectangle)}");
Console.WriteLine($"Circle Area: {calculator.Calculate(circle)}");
Console.WriteLine($"Triangle Area: {calculator.Calculate(triangle)}");
  1. Liskov substitution principle(LSP) Liskov替換
    如果 S 是 T 的子類型,那麼在不改變程式正確性的前提下,我們應該能夠將所有類型為 T 的對象都替換成類型為 S 的對象。簡單說,子類別必須能夠完全替換其父類別,而不會導致程式出現異常行為。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
// 正確的 LSP 實現
// 基礎鳥類介面
public interface IBird
{
void Move();
void MakeSound();
}

// 會飛的鳥介面
public interface IFlyingBird : IBird
{
void Fly();
}

// 企鵝類別
public class Penguin : IBird
{
public void Move()
{
Console.WriteLine("Penguin is walking");
}

public void MakeSound()
{
Console.WriteLine("Penguin makes a sound");
}
}

// 麻雀類別
public class Sparrow : IFlyingBird
{
public void Move()
{
Console.WriteLine("Sparrow is moving");
}

public void MakeSound()
{
Console.WriteLine("Sparrow chirps");
}

public void Fly()
{
Console.WriteLine("Sparrow is flying");
}
}

// 鳥類管理器
public class BirdManager
{
public void MakeBirdMove(IBird bird)
{
bird.Move();
bird.MakeSound();
}

public void MakeBirdFly(IFlyingBird bird)
{
bird.Fly();
}
}

// 錯誤的 LSP 實現示範
public abstract class Bird
{
public abstract void Fly(); // 這違反了 LSP,因為不是所有鳥都能飛
}

public class Ostrich : Bird // 這是錯誤的設計
{
public override void Fly()
{
throw new NotImplementedException("Ostriches can't fly!"); // 違反 LSP
}
}
  1. Interface Segregation Principle(ISP) 介面隔離

介面隔離原則 (ISP) 是指不應該強迫客戶端依賴它不需要的介面。換句話說,一個類別不應該被強迫實作它用不到的方法。這個原則建議將大型介面分割成更小、更具體的介面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
// 錯誤的設計:一個大型介面包含所有功能
public interface IAllInOnePrinter
{
void Print(string document);
void Scan(string document);
void Fax(string document);
void PrintDuplex(string document);
void ScanColor(string document);
void ScanDuplex(string document);
}

// 這會強迫基本印表機實現不需要的功能
public class BasicPrinter : IAllInOnePrinter // 違反 ISP
{
public void Print(string document)
{
Console.WriteLine("Printing document");
}

public void Scan(string document)
{
throw new NotImplementedException("Basic printer cannot scan!");
}

public void Fax(string document)
{
throw new NotImplementedException("Basic printer cannot fax!");
}

public void PrintDuplex(string document)
{
throw new NotImplementedException("Basic printer cannot print duplex!");
}

public void ScanColor(string document)
{
throw new NotImplementedException("Basic printer cannot scan in color!");
}

public void ScanDuplex(string document)
{
throw new NotImplementedException("Basic printer cannot scan duplex!");
}
}

// 正確的設計:將介面分割成多個小介面
public interface IPrinter
{
void Print(string document);
}

public interface IScanner
{
void Scan(string document);
}

public interface IFax
{
void Fax(string document);
}

public interface IDuplexPrinter
{
void PrintDuplex(string document);
}

public interface IColorScanner
{
void ScanColor(string document);
}

public interface IDuplexScanner
{
void ScanDuplex(string document);
}
  1. Dependency Inversion Principle(DIP) 依賴反轉

    依賴反轉原則有兩個核心概念:

    1. 高層模組不應依賴低層模組,兩者都應該依賴於抽象
    2. 抽象不應該依賴於細節,細節應該依賴於抽象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
// 錯誤的設計:高層模組直接依賴低層模組
public class EmailService
{
public void SendEmail(string message)
{
Console.WriteLine($"Sending email: {message}");
}
}

public class NotificationService // 違反 DIP
{
private EmailService emailService;

public NotificationService()
{
emailService = new EmailService(); // 直接依賴具體實現
}

public void SendNotification(string message)
{
emailService.SendEmail(message); // 強耦合
}
}

// 正確的設計:使用依賴反轉原則
// 1. 定義抽象介面
public interface IMessageService
{
void SendMessage(string message);
}

// 2. 具體實現依賴於抽象
public class EmailServiceImpl : IMessageService
{
public void SendMessage(string message)
{
Console.WriteLine($"Sending email: {message}");
}
}

public class SMSServiceImpl : IMessageService
{
public void SendMessage(string message)
{
Console.WriteLine($"Sending SMS: {message}");
}
}

public class SlackServiceImpl : IMessageService
{
public void SendMessage(string message)
{
Console.WriteLine($"Sending Slack message: {message}");
}
}

// 3. 高層模組依賴於抽象
public class NotificationManager
{
private readonly IMessageService _messageService;

// 使用建構子注入依賴
public NotificationManager(IMessageService messageService)
{
_messageService = messageService;
}

public void SendNotification(string message)
{
_messageService.SendMessage(message);
}
}

// 4. 使用工廠模式來管理依賴
public class MessageServiceFactory
{
public static IMessageService CreateMessageService(string type)
{
return type.ToLower() switch
{
"email" => new EmailServiceImpl(),
"sms" => new SMSServiceImpl(),
"slack" => new SlackServiceImpl(),
_ => throw new ArgumentException("Unknown message service type")
};
}
}

// 5. 使用依賴注入容器(這裡用簡單實現示範)
public class DependencyContainer
{
private readonly Dictionary<Type, Type> _dependencies = new Dictionary<Type, Type>();

public void Register<TInterface, TImplementation>() where TImplementation : TInterface
{
_dependencies[typeof(TInterface)] = typeof(TImplementation);
}

public T Resolve<T>()
{
Type type = typeof(T);
if (_dependencies.ContainsKey(type))
{
return (T)Activator.CreateInstance(_dependencies[type]);
}
throw new Exception($"No registration found for {type}");
}
}