• 티스토리 홈
  • 프로필사진
    유니얼
  • 방명록
  • 공지사항
  • 태그
  • 블로그 관리
  • 글 작성
유니얼
  • 프로필사진
    유니얼
    • 분류 전체보기 (295)
      • Unity (17)
        • 게임 개발 (5)
      • Unreal (24)
        • 게임 개발 (20)
      • DirectX (36)
      • 코딩테스트 (91)
        • 프로그래머스 (25)
        • 백준 (66)
      • Google Workspace (1)
      • Programing (102)
        • C# (68)
        • C++ (24)
        • JavaScript (10)
      • 게임 서버 프로그래밍 (17)
      • Web (6)
        • 슈퍼코딩 (6)
  • 방문자 수
    • 전체:
    • 오늘:
    • 어제:
  • 최근 댓글
    등록된 댓글이 없습니다.
  • 최근 공지
    등록된 공지가 없습니다.
# Home
# 공지사항
#
# 태그
# 검색결과
# 방명록
  • 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 원칙은 객체 지향 설계에서 가장 중요한 지침 중 하나입니다. 이 원칙들을 적절히 적용하면, 유연하고 유지 보수가 쉬운 시스템을 구축할 수 있습니다. 각 원칙을 이해하고 프로젝트에 적용함으로써, 더 나은 소프트웨어 설계를 추구할 수 있습니다.

     

    반응형
    다음글
    다음 글이 없습니다.
    이전글
    이전 글이 없습니다.
    댓글
조회된 결과가 없습니다.
스킨 업데이트 안내
현재 이용하고 계신 스킨의 버전보다 더 높은 최신 버전이 감지 되었습니다. 최신버전 스킨 파일을 다운로드 받을 수 있는 페이지로 이동하시겠습니까?
("아니오" 를 선택할 시 30일 동안 최신 버전이 감지되어도 모달 창이 표시되지 않습니다.)
목차
표시할 목차가 없습니다.
    • 안녕하세요
    • 감사해요
    • 잘있어요

    티스토리툴바