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

SOLID:객체 지향 설계 원칙

SOLID는 객체 지향 프로그래밍에서 클래스 설계를 위한 니모닉 약어입니다. 원칙은 좋은 프로그래밍 습관과 유지 관리 가능한 코드를 개발하는 데 도움이 되는 관행을 확립합니다.

SOLID 원칙은 장기적으로 코드 유지 관리 및 확장성을 고려하여 Agile 코드 개발 환경을 풍부하게 합니다. 코드 종속성을 고려하고 최적화하면 보다 간단하고 조직적인 소프트웨어 개발 수명 주기를 만드는 데 도움이 됩니다.

SOLID 원칙이란 무엇입니까?

SOLID는 클래스 디자인을 위한 일련의 원칙을 나타냅니다. Robert C. Martin(Uncle Bob)은 대부분의 디자인 원칙을 도입하고 약어를 만들었습니다.
솔리드는 다음을 의미합니다.

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

SOLID:5가지 원칙 설명

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

아래의 각 섹션에서는 Python 프로그래밍 언어의 각 SOLID 원칙에 대한 개요를 제공합니다. SOLID의 일반적인 개념은 PHP, Java 또는 C#과 같은 모든 객체 지향 언어에 적용됩니다. 규칙을 일반화하면 마이크로서비스와 같은 최신 프로그래밍 접근 방식에 적용할 수 있습니다.

단일 책임 원칙(SRP)

단일 책임 원칙(SRP)은 다음과 같이 명시합니다. "클래스가 변경되는 이유는 한 가지 이상 있어서는 안 됩니다."

클래스를 변경할 때 하나의 기능만 변경해야 합니다. 즉, 모든 객체에는 하나의 작업만 있어야 합니다.

예를 들어 다음 클래스를 살펴보세요.

# A class with multiple responsibilities
class Animal:
    # Property constructor
    def __init__(self, name):
        self.name = name

    # Property representation
    def __repr__(self):
        return f'Animal(name="{self.name}")'

    # Database management
    def save(animal):
        print(f'Saved {animal} to the database')

if __name__ == '__main__':
    # Property instantiation
    a = Animal('Cat')
    # Saving property to a database
    Animal.save(a)

save()을 변경할 때 메소드에서 변경은 Animal에서 발생합니다. 수업. 속성을 변경할 때 Animal에서도 수정됩니다. 수업.

클래스가 변경되는 두 가지 이유가 있으며 단일 책임 원칙을 위반합니다. 코드가 예상대로 작동하더라도 디자인 원칙을 준수하지 않으면 장기적으로 코드를 관리하기가 더 어려워집니다.

단일 책임 원칙을 구현하려면 예제 클래스에 두 가지 작업이 있습니다.

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

# A class responsible for property management
class Animal:
    def __init__(self, name):
        self.name = name
    def __repr__(self):
        return f'Animal(name="{self.name}")'

# A class responsible for database management
class AnimalDB:
    def save(self, animal):
        print(f'Saved {animal} to the database')

if __name__ == '__main__':
    # Property instantiation
    a = Animal('Cat')
    # Database instantiation
    db = AnimalDB()
    # Saving property to a database
    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을 제거해야 합니다. 클래스를 만들고 특정 파일 형식으로 저장하기 위한 개별 클래스를 만듭니다.

다음 코드는 개방형 원칙의 적용을 보여줍니다.

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)

Liskov 대체 원칙(LSP)은 다음과 같이 명시합니다. "기본 클래스에 대한 포인터 또는 참조를 사용하는 함수는 자신도 모르게 파생 클래스의 개체를 사용할 수 있어야 합니다."

기능상의 눈에 띄는 변화 없이 부모 클래스가 자식 클래스를 대체할 수 있다는 원칙입니다.

아래 파일 작성 예를 확인하세요.

# Parent class
class FileHandling():
    def write_db(self):
        return f'Handling DB'
    def write_csv(self):
        return f'Handling CSV'

# Child classes
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__":
   # Parent class instantiation and function calls
    db = FileHandling()
    csv = FileHandling()
    print(db.write_db())
    print(db.write_csv())

    # Children classes instantiations and function calls
    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). ). 그러나 두 자식 모두 부적절한 쓰기 기능을 사용하려고 하면 오류가 발생합니다. 이는 재정의하는 기능이 부모 기능과 일치하지 않기 때문에 Liskov 대체 원칙을 위반하는 것입니다.

다음 코드는 문제를 해결합니다.

# Parent class
class FileHandling():
    def write(self):
        return f'Handling file'

# Child classes
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__":
   # Parent class instantiation and function calls
    db = FileHandling()
    csv = FileHandling()
    print(db.write())
    print(csv.write())

    # Children classes instantiations and function calls
    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 행동 양식. 특정 동물에는 두 가지 기능이 모두 허용되지만 일부 동물에는 중복 기능이 있습니다.

상황을 처리하려면 인터페이스를 더 작은 섹션으로 분할하십시오. 예:

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()

예제에는 두 개의 클래스가 있습니다.

종속성 반전 원칙은 두 클래스 사이에 추상화 인터페이스 계층을 추가해야 합니다.

예시 솔루션은 다음과 같습니다.

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 클래스는 이제 NameConverter에 종속됩니다. LatinConverter 대신 인터페이스 곧장. 향후 업데이트에서는 NameConverter를 통해 다른 언어와 API를 사용하여 이름 변환을 정의할 수 있습니다. 인터페이스.

SOLID 원칙이 필요한 이유는 무엇입니까?

SOLID 원칙은 디자인 패턴 문제를 해결하는 데 도움이 됩니다. SOLID 원칙의 전반적인 목표는 코드 종속성을 줄이는 것이며 새로운 기능을 추가하거나 코드의 일부를 변경해도 전체 빌드가 중단되지 않습니다.

SOLID 원칙을 객체 지향 설계에 적용한 결과 코드를 보다 쉽게 ​​이해, 관리, 유지 및 변경할 수 있습니다. 이 규칙은 대규모 프로젝트에 더 적합하므로 SOLID 원칙을 적용하면 전체 개발 수명 주기 속도와 효율성이 향상됩니다.

SOLID 원칙은 여전히 ​​관련성이 있습니까?

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

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

결론

SOLID 원칙은 소프트웨어 아키텍처 설계를 위한 좋은 프레임워크와 지침을 제공하는 데 도움이 됩니다. 이 가이드의 예는 Python과 같이 동적으로 유형이 지정된 언어라도 코드 디자인에 원칙을 적용하면 이점이 있음을 보여줍니다.

다음으로 팀이 DevOps를 최대한 활용하는 데 도움이 되는 9가지 DevOps 원칙에 대해 읽어보세요.


클라우드 컴퓨팅

  1. C# 정적 키워드
  2. C# 중첩 클래스
  3. C++ 클래스 템플릿
  4. 자바 익명 클래스
  5. 자바 ObjectOutputStream 클래스
  6. 자바 제네릭
  7. 자바 파일 클래스
  8. 데이터 집약적인 애플리케이션에 강력한 상호 연결을 적용하기 위한 5가지 설계 원칙
  9. C# - 상속
  10. C# - 다형성