-
C# 프로그래밍 : SOLID 원칙2024년 08월 24일
- 유니얼
-
작성자
-
2024.08.24.:07
728x90객체 지향 프로그래밍의 성공은 효과적인 클래스 설계에 크게 좌우됩니다. 이를 위해 SOLID 원칙이라는 다섯 가지 기본 원칙이 제안되었으며, 이 원칙들은 코드의 유연성, 유지 보수성, 확장성을 높이는 데 크게 기여합니다. 이 블로그 글에서는 각 원칙을 설명하고, C#을 사용한 예제를 통해 이 원칙들이 실제 코드에 어떻게 적용될 수 있는지 살펴보겠습니다.
1. 단일 책임 원칙 (Single Responsibility Principle, SRP)
단일 책임 원칙은 클래스가 오직 하나의 책임을 가져야 한다는 원칙입니다. 이 원칙에 따르면, 클래스를 변경해야 하는 이유는 단 하나여야 합니다. 이는 클래스의 응집도를 높이고, 다른 기능에 의해 클래스가 변경될 필요가 없도록 함으로써 유지 관리를 쉽게 만듭니다.
예제:
public class UserManager { public void AddUser(string username) { Console.WriteLine($"User '{username}' added."); } }
UserManager 클래스는 사용자 관리와 관련된 책임만을 가집니다.
2. 개방/폐쇄 원칙 (Open/Closed Principle, OCP)
클래스는 확장에는 열려 있어야 하지만, 수정에는 닫혀 있어야 합니다. 이 원칙은 기존의 코드를 변경하지 않으면서 시스템의 기능을 확장할 수 있도록 돕습니다.
예제:
public abstract class DiscountCalculator { public abstract double CalculateDiscount(double totalPrice); } public class PercentageDiscount : DiscountCalculator { private double percentage; public PercentageDiscount(double percentage) { this.percentage = percentage; } public override double CalculateDiscount(double totalPrice) { return totalPrice - (totalPrice * percentage / 100); } }
DiscountCalculator 추상 클래스는 확장 가능하며, 신규 할인 계산 로직을 추가하는 것이 쉽습니다.
3. 리스코프 치환 원칙 (Liskov Substitution Principle, LSP)
서브 클래스는 기반 클래스의 대체가 가능해야 합니다. 이 원칙은 상속을 사용할 때, 서브 클래스가 기반 클래스의 계약을 위반하지 않도록 보장합니다.
예제:
public class Rectangle { public virtual int Area() => Width * Height; } public class Square : Rectangle { public override int Area() => Width * Width; }
Rectangle과 Square는 Shape를 상속받으며, 서로 대체 가능해야 합니다.
4. 인터페이스 분리 원칙 (Interface Segregation Principle, ISP)
클라이언트는 사용하지 않는 메서드에 의존하지 않아야 합니다. 이 원칙은 인터페이스를 작고 구체적인 단위로 분리하여, 클라이언트가 필요로 하는 메서드만을 구현하도록 합니다.
예제:
public interface IPrinter { void Print(string content); } public interface IScanner { void Scan(string document); } public class MultiFunctionPrinter : IPrinter, IScanner { public void Print(string content) { Console.WriteLine($"Printing: {content}"); } public void Scan(string document) { Console.WriteLine($"Scanning: {document}"); } }
각 인터페이스는 명확한 책임을 가지며, MultiFunctionPrinter는 두 인터페이스를 구현합니다.
5. 의존성 역전 원칙 (Dependency Inversion Principle, DIP)
고수준 모듈은 저수준 모듈에 의존해서는 안 되며, 둘 다 추상화에 의존해야 합니다. 이 원칙은 설계의 유연성을 높이고, 컴포넌트 간의 결합도를 낮추는 데 도움을 줍니다.
예제:
public interface ISender { void Send(string message); } public class EmailSender : ISender { public void Send(string message) { Console.WriteLine($"Sending email: {message}"); } } public class NotificationService { private ISender _sender; public NotificationService(ISender sender) { _sender = sender; } public void Notify(string message) { _sender.Send(message); } }
NotificationService는 ISender 인터페이스에 의존하며, 이를 통해 다양한 메시지 전송 방식을 쉽게 통합할 수 있습니다.
전체예제 코드
using System; namespace CSharp_ProgramingStudy.Chapter5_OOP { public class Class12 { /// <summary> /// 1. 단일 책임 원칙 (Single Responsibility Principle, SRP) /// - 설명: 클래스는 단 하나의 책임만 가져야 하며, 그 책임을 완전히 캡슐화해야 합니다. /// 이는 클래스가 변화해야 할 이유가 오직 하나뿐이어야 함을 의미합니다. /// - 예제: 다음 예제에서 UserManager 클래스는 사용자를 관리하는 책임만 가지고 있습니다. /// </summary> public class UserManager { public void AddUser(string username) { Console.WriteLine($"User '{username}' added."); } } /// <summary> /// 2. 개방/폐쇄 원칙 (Open/Closed Principle, OCP) /// - 설명: 클래스는 확장에는 열려 있어야 하지만, 수정에는 닫혀 있어야 합니다. /// 즉, 새로운 기능을 추가할 때 기존 코드를 수정하지 않고도 확장할 수 있어야 합니다. /// - 예제: 다음 예제에서, DiscountCalculator 클래스는 새로운 할인 정책을 추가할 때 기존 코드를 수정하지 않고도 확장 가능합니다. /// </summary> public abstract class DiscountCalculator { public abstract double CalculateDiscount(double totalPrice); } public class NoDiscount : DiscountCalculator { public override double CalculateDiscount(double totalPrice) { return totalPrice; } } public class PercentageDiscount : DiscountCalculator { private double percentage; public PercentageDiscount(double percentage) { this.percentage = percentage; } public override double CalculateDiscount(double totalPrice) { return totalPrice - (totalPrice * percentage / 100); } } /// <summary> /// 3. 리스코프 치환 원칙 (Liskov Substitution Principle, LSP) /// - 설명: 서브 클래스는 언제나 자신의 기반 클래스 타입으로 대체할 수 있어야 합니다. /// 즉, 서브 클래스는 기반 클래스의 행동을 그대로 유지하면서 확장해야 합니다. /// - 예제: 다음 예제에서, Rectangle과 Square 클래스는 모두 Shape 클래스를 상속받고 있으며, Shape 타입으로 대체 가능해야 합니다. /// </summary> public class Shape { public virtual int Area() => 0; } public class Rectangle : Shape { public int Width { get; set; } public int Height { get; set; } public override int Area() => Width * Height; } public class Square : Rectangle { public override int Area() => Width * Width; } /// <summary> /// 4. 인터페이스 분리 원칙 (Interface Segregation Principle, ISP) /// - 설명: 특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫습니다. /// 즉, 클라이언트는 자신이 사용하지 않는 메서드에 의존하지 않아야 합니다. /// - 예제: 다음 예제에서, IPrinter는 인쇄 기능만 제공하고, IScanner는 스캔 기능만 제공합니다. /// </summary> public interface IPrinter { void Print(string content); } public interface IScanner { void Scan(string document); } public class MultiFunctionPrinter : IPrinter, IScanner { public void Print(string content) { Console.WriteLine($"Printing: {content}"); } public void Scan(string document) { Console.WriteLine($"Scanning: {document}"); } } /// <summary> /// 5. 의존성 역전 원칙 (Dependency Inversion Principle, DIP) /// - 설명: 고수준 모듈은 저수준 모듈에 의존해서는 안 되며, 둘 다 추상화에 의존해야 합니다. /// 즉, 구현이 아닌 추상화에 의존해야 합니다. /// - 예제: 다음 예제에서, NotificationService는 EmailSender와 SmsSender와 같은 저수준 구현에 의존하지 않고, ISender 인터페이스에 의존합니다. /// </summary> public interface ISender { void Send(string message); } public class EmailSender : ISender { public void Send(string message) { Console.WriteLine($"Sending email: {message}"); } } public class SmsSender : ISender { public void Send(string message) { Console.WriteLine($"Sending SMS: {message}"); } } public class NotificationService { private readonly ISender _sender; public NotificationService(ISender sender) { _sender = sender; } public void Notify(string message) { _sender.Send(message); } } /// <summary> /// Run 메서드: 각 설계 원칙의 예제를 실행 /// </summary> public void Run() { // SRP 예제 실행 UserManager userManager = new UserManager(); userManager.AddUser("JohnDoe"); // OCP 예제 실행 DiscountCalculator calculator = new PercentageDiscount(10); Console.WriteLine($"Discounted Price: {calculator.CalculateDiscount(100)}"); // LSP 예제 실행 Shape rectangle = new Rectangle { Width = 4, Height = 5 }; Shape square = new Square { Width = 4 }; Console.WriteLine($"Rectangle Area: {rectangle.Area()}"); Console.WriteLine($"Square Area: {square.Area()}"); // ISP 예제 실행 MultiFunctionPrinter mfp = new MultiFunctionPrinter(); mfp.Print("Document"); mfp.Scan("Photo"); // DIP 예제 실행 NotificationService notificationService = new NotificationService(new EmailSender()); notificationService.Notify("Hello World!"); } } }
결론
SOLID 원칙은 객체 지향 설계에서 가장 중요한 지침 중 하나입니다. 이 원칙들을 적절히 적용하면, 유연하고 유지 보수가 쉬운 시스템을 구축할 수 있습니다. 각 원칙을 이해하고 프로젝트에 적용함으로써, 더 나은 소프트웨어 설계를 추구할 수 있습니다.
반응형다음글이전글이전 글이 없습니다.댓글