산업 제조
산업용 사물 인터넷 | 산업자재 | 장비 유지 보수 및 수리 | 산업 프로그래밍 |
home  MfgRobots >> 산업 제조 >  >> Industrial Internet of Things >> 클라우드 컴퓨팅

SOLID 원칙: 깔끔하고 유지보수하기 쉬운 코드를 위한 객체지향 디자인 마스터하기

SOLID는 객체지향 프로그래밍에서 클래스 설계를 위한 니모닉 약어입니다. 이 원칙들은 장기적으로 코드 유지보수와 확장성을 고려하여 좋은 프로그래밍 습관과 유지보수하기 쉬운 코드 개발을 돕는 관행을 수립합니다.

코드 의존성을 고려하고 최적화함으로써 SOLID 원칙은 더 단순하고 조직적인 소프트웨어 개발 수명주기를 생성하는 데 도움을 줍니다.

SOLID 원칙: 깔끔하고 유지보수하기 쉬운 코드를 위한 객체지향 디자인 마스터하기

SOLID 원칙이란 무엇인가?

SOLID는 클래스 설계를 위한 원칙 세트를 나타냅니다. Robert C. Martin(Uncle Bob)이 대부분의 설계 원칙을 소개하고 약어를 만들었습니다.
SOLID는 다음을 의미합니다:

SOLID 원칙은 소프트웨어 설계를 위한 모범 사례 모음을 나타냅니다. 각 아이디어는 더 나은 프로그래밍 습관, 개선된 코드 설계, 더 적은 오류로 이어지는 설계 프레임워크를 나타냅니다.

SOLID: 5가지 원칙 설명

SOLID 원칙이 어떻게 작동하는지 이해하는 가장 좋은 방법은 예제를 통하는 것입니다. 모든 원칙은 상호 보완적이며 개별 사용 사례에 적용됩니다. 원칙 적용 순서는 중요하지 않으며, 모든 원칙이 모든 상황에 적용되는 것은 아닙니다.

SOLID 원칙: 깔끔하고 유지보수하기 쉬운 코드를 위한 객체지향 디자인 마스터하기

아래 각 섹션은 Python 프로그래밍 언어로 각 SOLID 원칙에 대한 개요를 제공합니다. SOLID의 일반적인 아이디어는 Java, C#, PHP 등 모든 객체지향 언어에 적용됩니다. 규칙을 일반화하면 마이크로서비스 같은 현대 프로그래밍 접근 방식에도 적용 가능하게 만듭니다.

단일 책임 원칙(SRP)

단일 책임 원칙(SRP)은 다음과 같이 말합니다: "클래스가 변경되어야 할 이유는 결코 하나 이상이 없어야 한다."

클래스를 변경할 때 단일 기능만 변경해야 하며, 이는 모든 객체가 오직 하나의 작업만 가져야 함을 의미합니다.

다음 클래스를 예로 들어보겠습니다:

# 여러 책임을 가진 클래스 class Animal: # 속성 생성자 def __init__(self, name): self.name = name # 속성 표현 def __repr__(self): return f'Animal(name="{self.name}")' # 데이터베이스 관리 def save(animal): print(f'Saved {animal} to the database') if __name__ == '__main__': # 속성 인스턴스화 a = Animal('Cat') # 속성을 데이터베이스에 저장 Animal.save(a) 

save() 메서드에 변경 사항이 생기면 변경은 Animal 클래스에서 발생합니다. 속성 변경 시 수정 사항도 Animal 클래스에서 발생합니다.

이 클래스는 변경 이유가 두 가지이므로 단일 책임 원칙을 위반합니다. 코드가 예상대로 작동하더라도 설계 원칙을 존중하지 않으면 장기적으로 코드 관리가 더 어려워집니다.

단일 책임 원칙을 구현하려면 예제 클래스가 두 가지 뚜렷한 일을 하고 있음을 알 수 있습니다:

SOLID 원칙: 깔끔하고 유지보수하기 쉬운 코드를 위한 객체지향 디자인 마스터하기

따라서 이 문제를 해결하는 가장 좋은 방법은 데이터베이스 관리 메서드를 새 클래스로 분리하는 것입니다. 예를 들어:

# 속성 관리를 담당하는 클래스 class Animal: def __init__(self, name): self.name = name def __repr__(self): return f'Animal(name="{self.name}")' # 데이터베이스 관리를 담당하는 클래스 class AnimalDB: def save(self, animal): print(f'Saved {animal} to the database') if __name__ == '__main__': # 속성 인스턴스화 a = Animal('Cat') # 데이터베이스 인스턴스화 db = AnimalDB() # 속성을 데이터베이스에 저장 db.save(a) 

단일 책임 원칙 적용으로 AnimalDB 클래스 변경이 Animal 클래스에 영향을 주지 않습니다. 코드는 직관적이고 수정하기 쉽습니다.

개방-폐쇄 원칙(OCP)

개방-폐쇄 원칙(OCP)은 다음과 같이 말합니다: "소프트웨어 엔티티는 확장에 대해 열려 있어야 하지만 수정에 대해서는 닫혀 있어야 한다."

시스템에 새로운 기능과 사용 사례를 추가하는 것이 기존 엔티티 수정을 요구하지 않아야 합니다. 표현이 모순되어 보입니다. 새로운 기능을 추가하려면 기존 코드 변경이 필요해 보이기 때문입니다.

다음 예제를 통해 아이디어를 쉽게 이해할 수 있습니다:

class Animal: def __init__(self, name): self.name = name def __repr__(self): return f'Animal(name="{self.name}")' class Storage: def save_to_db(self, animal): print(f'Saved {animal} to the database') 

Storage 클래스는 Animal 인스턴스의 정보를 데이터베이스에 저장합니다. CSV 파일에 저장 같은 새로운 기능 추가는 Storage 클래스에 코드 추가를 요구합니다:

class Animal: def __init__(self, name): self.name = name def __repr__(self): return f'Animal(name="{self.name}")' class Storage: def save_to_db(self, animal): print(f'Saved {animal} to the database') def save_to_csv(self,animal): printf(f’Saved {animal} to the CSV file’) 

save_to_csv 메서드는 새로운 기능 추가를 위해 기존 Storage 클래스를 수정합니다. 이 접근은 기존 요소를 변경하여 새로운 기능이 나타날 때 개방-폐쇄 원칙을 위반합니다.

코드는 범용 Storage 클래스를 제거하고 특정 파일 형식 저장을 위한 개별 클래스 생성을 요구합니다.

SOLID 원칙: 깔끔하고 유지보수하기 쉬운 코드를 위한 객체지향 디자인 마스터하기

다음 코드는 개방-폐쇄 원칙 적용을 보여줍니다:

class DB(): def save(self, animal): print(f'Saved {animal} to the database') class CSV(): def save(self, animal): print(f'Saved {animal} to a CSV file') 

코드는 개방-폐쇄 원칙을 준수합니다. 전체 코드는 이제 다음과 같습니다:

class Animal: def __init__(self, name): self.name = name def __repr__(self): return f'"{self.name}"' class DB(): def save(self, animal): print(f'Saved {animal} to the database') class CSV(): def save(self, animal): print(f'Saved {animal} to a CSV file') if __name__ == '__main__': a = Animal('Cat') db = DB() csv = CSV() db.save(a) csv.save(a) 

추가 기능(예: XML 파일 저장)으로 확장해도 기존 클래스 수정하지 않음.

리스코프 치환 원칙(LSP)

리스코프 치환 원칙(LSP)은 다음과 같이 말합니다: "기본 클래스에 대한 포인터나 참조를 사용하는 함수는 이를 알지 못한 채 파생 클래스의 객체를 사용할 수 있어야 한다."

이 원칙은 부모 클래스가 기능에 눈에 띄는 변경 없이 자식 클래스를 대체할 수 있어야 함을 의미합니다.

아래 파일 쓰기 예제를 확인하세요:

# 부모 클래스 class FileHandling(): def write_db(self): return f'Handling DB' def write_csv(self): return f'Handling CSV' # 자식 클래스 class WriteDB(FileHandling): def write_db(self): return f'Writing to a DB' def write_csv(self): return f"Error: Can't write to CSV, wrong file type." class WriteCSV(FileHandling): def write_csv(self): return f'Writing to a CSV file' def write_db(self): return f"Error: Can't write to DB, wrong file type." if __name__ == "__main__": # 부모 클래스 인스턴스화 및 함수 호출 db = FileHandling() csv = FileHandling() print(db.write_db()) print(db.write_csv()) # 자식 클래스 인스턴스화 및 함수 호출 db = WriteDB() csv = WriteCSV() print(db.write_db()) print(db.write_csv()) print(csv.write_db()) print(csv.write_csv()) 

부모 클래스(FileHandling)는 데이터베이스와 CSV 파일에 쓰기 위한 두 메서드로 구성됩니다. 클래스는 두 기능 모두 처리하고 메시지를 반환합니다.

두 자식 클래스(WriteDB와 WriteCSV)는 부모 클래스(FileHandling)에서 속성을 상속받습니다. 그러나 두 자식 모두 부적절한 쓰기 함수 사용 시 오류를 던지며, 이는 재정의 함수가 부모 함수와 대응하지 않으므로 리스코프 치환 원칙을 위반합니다.

SOLID 원칙: 깔끔하고 유지보수하기 쉬운 코드를 위한 객체지향 디자인 마스터하기

다음 코드가 이슈를 해결합니다:

# 부모 클래스 class FileHandling(): def write(self): return f'Handling file' # 자식 클래스 class WriteDB(FileHandling): def write(self): return f'Writing to a DB' class WriteCSV(FileHandling): def write(self): return f'Writing to a CSV file' if __name__ == "__main__": # 부모 클래스 인스턴스화 및 함수 호출 db = FileHandling() csv = FileHandling() print(db.write()) print(csv.write()) # 자식 클래스 인스턴스화 및 함수 호출 db = WriteDB() csv = WriteCSV() print(db.write()) print(csv.write()) 

자식 클래스가 부모 함수와 올바르게 대응합니다.

인터페이스 분리 원칙(ISP)

인터페이스 분리 원칙(ISP)은 다음과 같이 말합니다: "하나의 범용 인터페이스보다 많은 클라이언트별 인터페이스가 낫다."

즉, 더 광범위한 상호작용 인터페이스는 더 작은 것들로 분할됩니다. 이 원칙은 클래스가 필요한 메서드만 사용하도록 하여 전체 중복을 줄입니다.

다음 예제는 범용 인터페이스를 보여줍니다:

class Animal(): def walk(self): pass def swim(self): pass class Cat(Animal): def walk(self): print("Struts") def fly(self): raise Exception("Cats don't swim") class Duck(Animal): def walk(self): print("Waddles") def swim(self): print("Floats") 

자식 클래스들은 부모 Animal 클래스에서 상속받으며, 이 클래스는 walk와 fly 메서드를 포함합니다. 두 함수 모두 특정 동물에겐 허용되지만, 일부 동물에겐 중복 기능입니다.

SOLID 원칙: 깔끔하고 유지보수하기 쉬운 코드를 위한 객체지향 디자인 마스터하기

상황을 처리하려면 인터페이스를 더 작은 섹션으로 분할하세요. 예를 들어:

class Walk(): def walk(self): pass class Swim(Walk): def swim(self): pass class Cat(Walk): def walk(self): print("Struts") class Duck(Swim): def walk(self): print("Waddles") def swim(self): print("Floats") 

Fly 클래스는 Walk에서 상속받아 적절한 자식 클래스에 추가 기능 제공. 예제는 인터페이스 분리 원칙 만족.
물고기 같은 다른 동물 추가는 물고기가 걸을 수 없으므로 인터페이스를 더 원자화해야 함.

의존성 역전 원칙(DIP)

의존성 역전 원칙은 다음과 같이 말합니다: "구체화가 아닌 추상화에 의존하라."

이 원칙은 추상화 계층을 추가하여 클래스 간 연결을 줄이는 것을 목표로 합니다. 의존성을 추상화로 이동시키면 코드가 견고해집니다.

다음 예제는 추상화 계층 없이 클래스 의존성을 보여줍니다:

class LatinConverter: def latin(self, name): print(f'{name} = "Felis catus"') return "Felis catus" class Converter: def start(self): converter = LatinConverter() converter.latin('Cat') if __name__ == '__main__': converter = Converter() converter.start() 

예제에는 두 클래스가 있습니다:

의존성 역전 원칙은 두 클래스 사이에 추상화 인터페이스 계층 추가를 요구합니다.

SOLID 원칙: 깔끔하고 유지보수하기 쉬운 코드를 위한 객체지향 디자인 마스터하기

예제 해결책은 다음과 같습니다:

from abc import ABC class NameConverter(ABC): def convert(self,name): pass class LatinConverter(NameConverter): def convert(self, name): print('Converting using Latin API') print(f'{name} = "Felis catus"') return "Felis catus" class Converter: def __init__(self, converter: NameConverter): self.converter = converter def start(self): self.converter.convert('Cat') if __name__ == '__main__': latin = LatinConverter() converter = Converter(latin) converter.start() 

Converter 클래스는 이제 LatinConverter에 직접 의존하는 대신 NameConverter 인터페이스에 의존합니다. 향후 업데이트로 NameConverter 인터페이스를 통해 다른 언어와 API를 사용한 이름 변환 정의 가능.

왜 SOLID 원칙이 필요한가?

SOLID 원칙은 설계 패턴 문제와 싸우는 데 도움을 줍니다. SOLID 원칙의 전반적 목표는 코드 의존성을 줄이는 것이며, 새로운 기능 추가나 코드 일부 변경이 전체 빌드를 망가뜨리지 않게 합니다.

객체지향 설계에 SOLID 원칙 적용 결과 코드는 이해, 관리, 유지보수, 변경이 더 쉬워집니다. 규칙이 대규모 프로젝트에 더 적합하므로 SOLID 원칙 적용은 전반적 개발 수명주기 속도와 효율성을 증가시킵니다.

SOLID 원칙은 여전히 관련성이 있는가?

SOLID 원칙이 20년 넘었지만 여전히 소프트웨어 아키텍처 설계에 좋은 기반을 제공합니다. SOLID는 객체지향 프로그래밍뿐만 아니라 현대 프로그램과 환경에 적용 가능한 건전한 설계 원칙을 제공합니다.

SOLID 원칙은 코드가 사람에 의해 작성되고 수정되며, 모듈로 구성되고, 내부 또는 외부 요소를 포함하는 상황에 적용됩니다.

결론

SOLID 원칙은 소프트웨어 아키텍처 설계에 좋은 프레임워크와 가이드를 제공합니다. 이 가이드의 예제들은 Python 같은 동적 타입 언어조차 코드 설계에 원칙 적용으로 혜택을 본다는 것을 보여줍니다.

다음으로, 팀이 DevOps에서 최대한 활용할 수 있도록 돕는 9가지 DevOps 원칙을 읽어보세요.

클라우드 컴퓨팅

  1. C# static 키워드: 정적 변수, 메서드, 클래스 완벽 활용
  2. C# 중첩 클래스(Nested Class) 이해: 정의, 사용법, 상속
  3. C++ 클래스 템플릿 마스터: 실전 가이드
  4. Java 익명 내부 클래스 마스터: 정의, 문법, 실전 예제
  5. Java ObjectOutputStream 마스터하기: 직렬화, 메서드, 실전 예제
  6. Java 제네릭 마스터하기: 재사용 가능하고 타입 안전한 코드 작성
  7. java.io로 Java 파일 작업 마스터하기 – 생성, 읽기, 쓰기 및 삭제
  8. 고속 데이터 집약적 애플리케이션을 위한 견고한 인터커넥트 5대 설계 원칙
  9. C# 상속 마스터하기: 재사용 가능하고 유지보수하기 쉬운 코드 작성
  10. C# 다형성: 정적 바인딩 vs 동적 바인딩 완전 이해