개인적으로 잘 안지키거나 책을 읽으면서 어려웠던 부분에 대한 예제입니다.
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
public class Car
{
public Engine Engine { get; set; }
}
public class Engine
{
public FuelInjector FuelInjector { get; set; }
}
public class FuelInjector
{
public void Inject()
{
Console.WriteLine("연료 주입");
}
}
public class Driver
{
public void Drive(Car car)
{
car.Engine.FuelInjector.Inject();
}
}
이 예시에서 Driver
클래스의 Drive
메서드는 Car
객체의 내부 구조를 너무 많이 알고 있어 디미터 법칙을 위반합니다.
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
public class Car
{
private Engine _engine;
public Car()
{
_engine = new Engine();
}
public void StartEngine()
{
_engine.Start();
}
}
public class Engine
{
private FuelInjector _fuelInjector;
public Engine()
{
_fuelInjector = new FuelInjector();
}
public void Start()
{
_fuelInjector.Inject();
}
}
public class FuelInjector
{
public void Inject()
{
Console.WriteLine("연료 주입");
}
}
public class Driver
{
public void Drive(Car car)
{
car.StartEngine();
}
}
각 객체가 자신의 직접적인 구성 요소와만 상호작용합니다.
Driver
는 Car
에게만 메시지를 보내고, Car
는 Engine
에게, Engine
은 FuelInjector
에게 메시지를 보냅니다.
객체의 내부 구조가 외부로 노출되지 않고 객체 간의 의존성이 줄어들었습니다.
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
// 외부 결제 API (우리가 직접 수정할 수 없는 코드)
public class ExternalPaymentAPI
{
public bool ProcessPayment(string cardNumber, decimal amount, string currency)
{
Console.WriteLine($"외부 API: {amount} {currency}로 {cardNumber}에서 결제 처리");
return true;
}
}
// 우리의 결제 서비스
public class PaymentService
{
private readonly ExternalPaymentAPI _paymentAPI;
public PaymentService()
{
_paymentAPI = new ExternalPaymentAPI();
}
public void ProcessOrder(decimal orderAmount, string cardNumber)
{
if (_paymentAPI.ProcessPayment(cardNumber, orderAmount, "USD"))
{
Console.WriteLine("주문 처리 성공!");
}
else
{
Console.WriteLine("주문 처리 실패.");
}
}
}
// 사용 예시
class Program
{
static void Main(string[] args)
{
PaymentService paymentService = new PaymentService();
paymentService.ProcessOrder(100.50m, "1234-5678-9012-3456");
}
}
PaymentService
가 외부 API의 세부 구현에 직접 의존합니다.PaymentService
를 직접 수정해야 합니다.
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
// 외부 결제 API (변경 없음)
public class ExternalPaymentAPI
{
public bool ProcessPayment(string cardNumber, decimal amount, string currency)
{
Console.WriteLine($"외부 API: {amount} {currency}로 {cardNumber}에서 결제 처리");
return true;
}
}
// 우리 시스템의 결제 인터페이스
public interface IPaymentProcessor
{
bool MakePayment(decimal amount, string cardNumber);
}
// Adapter 구현
public class PaymentAdapter : IPaymentProcessor
{
private readonly ExternalPaymentAPI _externalPaymentAPI;
public PaymentAdapter()
{
_externalPaymentAPI = new ExternalPaymentAPI();
}
public bool MakePayment(decimal amount, string cardNumber)
{
return _externalPaymentAPI.ProcessPayment(cardNumber, amount, "USD");
}
}
// 개선된 결제 서비스
public class PaymentService
{
private readonly IPaymentProcessor _paymentProcessor;
public PaymentService(IPaymentProcessor paymentProcessor)
{
_paymentProcessor = paymentProcessor;
}
public void ProcessOrder(decimal orderAmount, string cardNumber)
{
if (_paymentProcessor.MakePayment(orderAmount, cardNumber))
{
Console.WriteLine("주문 처리 성공!");
}
else
{
Console.WriteLine("주문 처리 실패.");
}
}
}
// 사용 예시
class Program
{
static void Main(string[] args)
{
IPaymentProcessor paymentProcessor = new PaymentAdapter();
PaymentService paymentService = new PaymentService(paymentProcessor);
paymentService.ProcessOrder(100.50m, "1234-5678-9012-3456");
}
}
PaymentAdapter
내부로 캡슐화되었습니다.PaymentAdapter
만 수정하면 됩니다. PaymentService
는 변경할 필요가 없습니다.IPaymentProcessor
인터페이스를 구현하는 다른 Adapter를 만들어 쉽게 다른 결제 시스템으로 전환할 수 있습니다.IPaymentProcessor
의 mock 구현체를 사용하여 PaymentService
를 쉽게 단위 테스트할 수 있습니다.PaymentService
가 구체적인 구현이 아닌 추상화(IPaymentProcessor
)에 의존하게 되어 의존성 역전 원칙을 따릅니다.
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
// 모든 책임이 집중된 클래스
public class PaymentProcessor
{
private readonly FileLogger _logger = new FileLogger();
private readonly PayPalGateway _gateway = new PayPalGateway();
public void ProcessPayment(decimal amount, string customerId)
{
// SRP 위반: 결제 처리 + 로깅 + 알림 전송
try
{
// DIP 위반: 구체 구현에 직접 의존
_gateway.Charge(amount, customerId);
// OCP 위반: 새로운 결제 방식 추가시 코드 수정 필요
_logger.Log($"결제 성공: {amount}");
SendEmail(customerId, "결제 성공 알림");
}
catch (Exception ex)
{
_logger.Log($"결제 실패: {ex.Message}");
SendEmail(customerId, "결제 실패 알림");
}
}
private void SendEmail(string customerId, string message)
{
// SRP 위반: 이메일 전송 책임 추가
Console.WriteLine($"{customerId}에게 이메일 전송: {message}");
}
}
// 구체적인 구현 클래스
public class PayPalGateway
{
public void Charge(decimal amount, string customerId)
{
Console.WriteLine($"PayPal: {customerId}에서 {amount} 청구");
}
}
public class FileLogger
{
public void Log(string message)
{
File.AppendAllText("log.txt", $"{DateTime.Now}: {message}");
}
}
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
// 추상화 계층
public interface IPaymentGateway
{
void Charge(decimal amount, string customerId);
}
public interface ILogger
{
void Log(string message);
}
public interface INotificationService
{
void SendNotification(string customerId, string message);
}
// 구현체들
public class PayPalGateway : IPaymentGateway
{
public void Charge(decimal amount, string customerId)
{
Console.WriteLine($"PayPal: {customerId}에서 {amount} 청구");
}
}
public class StripeGateway : IPaymentGateway
{
public void Charge(decimal amount, string customerId)
{
Console.WriteLine($"Stripe: {customerId}에서 {amount} 청구");
}
}
public class FileLogger : ILogger
{
public void Log(string message)
{
File.AppendAllText("log.txt", $"{DateTime.Now}: {message}");
}
}
public class EmailNotificationService : INotificationService
{
public void SendNotification(string customerId, string message)
{
Console.WriteLine($"{customerId}에게 이메일 전송: {message}");
}
}
// SRP 준수 핵심 클래스
public class PaymentProcessor
{
private readonly IPaymentGateway _gateway;
private readonly ILogger _logger;
private readonly INotificationService _notification;
// DIP 준수: 추상화에 의존
public PaymentProcessor(
IPaymentGateway gateway,
ILogger logger,
INotificationService notification)
{
_gateway = gateway;
_logger = logger;
_notification = notification;
}
public void ProcessPayment(decimal amount, string customerId)
{
try
{
_gateway.Charge(amount, customerId);
_logger.Log($"결제 성공: {amount}");
_notification.SendNotification(customerId, "결제 성공 알림");
}
catch (Exception ex)
{
_logger.Log($"결제 실패: {ex.Message}");
_notification.SendNotification(customerId, "결제 실패 알림");
throw;
}
}
}
// 사용 예시
var processor = new PaymentProcessor(
new StripeGateway(), // OCP 준수: 새로운 결제 방식 추가시 이 부분만 변경
new FileLogger(),
new EmailNotificationService()
);
processor.ProcessPayment(100.50m, "customer123");
원칙 | 구현 방식 | 장점 |
---|---|---|
SRP | 결제 처리, 로깅, 알림 서비스를 각각 분리 | 책임 분리로 인한 유지보수성 향상 |
OCP |
IPaymentGateway 인터페이스 사용 |
새로운 결제 방식 추가시 기존 코드 수정 없이 확장 가능 (예: CryptoGateway 추가 시 PaymentProcessor 변경 불필요) |
DIP | 생성자 주입을 통한 의존성 역전 | 테스트 용이성 향상 (Mock 객체 주입 가능) |
요구사항 변경에 유연하게 대응할 수 있으며, 각 구성 요소는 독립적으로 개발/테스트/배포가 가능합니다.
IPaymentGateway
구현체만 교체ILogger
새 구현체 생성INotificationService
새 구현체 생성