프로그래밍/CS

[디자인패턴] Cohesion (응집도), Coupling (결합도)

choar 2022. 7. 26. 11:21
반응형

최근 비전공자로서 부족한 부분을 채우고자 CS 공부의 일환으로 네트워크를 공부했다. (대학교 한 학기 수업 분량 수강 완료!)

그러다 프론트엔드 개발자라면 네트워크/운영체제는 알면 좋은 것, 개발 방법론은 알면 안 답답한 것, 디자인 패턴 등은 모르면 답답한 것이라는 (개발 잘하는) 친구의 조언을 듣고 소프트웨어 디자인 패턴 공부를 우선순위에 두고 시작하게 되었다.

특히 ArjanCodes라는 유튜버의 영상이 디자인 패턴 공부하기 좋다고 추천받아 하루에 하나씩 보며 공부할 예정이다.

 

Cohesion and Coupling

  • Cohesion(응집도) : the degree to which elements of a certain class or function belong together
  • 직역하면 cohesion이란 클래스나 함수 내의 요소들이 함께 속하는 정도이다. 좀 더 풀어 설명하면, 모듈* 내의 모든 요소들이 하나의 기능을 수행하기 위해 구성되어 있는지의 정도를 나타낸다.
    • 모듈 : 소프트웨어를 기능별로 나누었을 때 그 나누어진 각 부분.

function with weak cohesion 예시

cohesion이 약한 함수의 예시이다. 한 함수에서 너무 많은 일들을 하고 있다.

cohesion이 강한 함수는 한 가지 일만 하고, 명확한 책임(clear responsibility)을 가진다. (ex. 코사인 계산 함수)

 

strong cohesion → 재사용 쉽고, 유지보수 쉽다.

 

 

  • Coupling(결합도) : a measure of how dependent two parts of your code are on each other
  • 코드에서 한 부분과 다른 부분이 서로 얼마나 의존하고 있는지의 정도를 나타낸다.

function with high coupling 예시

coupling이 강한 함수의 예시이다.

해당 함수에서는 email 객체의 header를 직접 확인하고 있다.

→ 따라서 email 객체에 강하게 결합(highly coupled)되어 있다.

 

high coupling → 코드의 한 부분을 바꾸면 많은 부분을 수정해야 한다.

예를 들어 위 코드에서 email 객체의 구조가 바뀌면 함수를 재작성해야 한다.

코드에 어느 정도의 coupling은 있을 수밖에 없다. 코드의 특정 부분들은 함께 동작해야 하기 때문이다.

그러나 coupling이 강할 수록 유지보수가 어려워진다.

loose coupling을 지향해야 한다.

 

  • coupling 줄이는 방법
    • 위 코드를 예로 들면, email 객체를 전부 넘겨주는 방법 대신 함수에서 필요한 데이터만 전달할 수 있다.
    • 또는 해당 함수를 email 객체 안으로 옮길 수 있다. 객체의 데이터와 직접적으로 연관되어 있기 때문이다. (coupling을 줄이고 모듈의 cohesion을 높이는 방법)

 

Example

# BEFORE

import string
import random

class VehicleRegistry:

    def generate_vehicle_id(self, length):
        return ''.join(random.choices(string.ascii_uppercase, k=length))

    def generate_vehicle_license(self, id):
        return f"{id[:2]}-{''.join(random.choices(string.digits, k=2))}-{''.join(random.choices(string.ascii_uppercase, k=2))}"


class Application:

    def register_vehicle(self, brand: string):
        # create a registry instance
        registry = VehicleRegistry()

        # generate a vehicle id of length 12
        vehicle_id = registry.generate_vehicle_id(12)

        # now generate a license plate for the vehicle
        # using the first two characters of the vehicle id
        license_plate = registry.generate_vehicle_license(vehicle_id)

        # compute the catalogue price
        catalogue_price = 0
        if brand == "Tesla Model 3":
            catalogue_price = 60000
        elif brand == "Volkswagen ID3":
            catalogue_price = 35000
        elif brand == "BMW 5":
            catalogue_price = 45000

        # compute the tax percentage (default 5% of the catalogue price, except for electric cars where it is 2%)
        tax_percentage = 0.05
        if brand == "Tesla Model 3" or brand == "Volkswagen ID3":
            tax_percentage = 0.02

        # compute the payable tax
        payable_tax = tax_percentage * catalogue_price

        # print out the vehicle registration information
        print("Registration complete. Vehicle information:")
        print(f"Brand: {brand}")
        print(f"Id: {vehicle_id}")
        print(f"License plate: {license_plate}")
        print(f"Payable tax: {payable_tax}")

app = Application()
app.register_vehicle("Volkswagen ID3")
  • register_vehicle이라는 함수에서 너무 많은 일을 하고 있다. (low cohesion)
  • catalogue price를 계산하는 부분, tax percentage 계산하는 부분이 if문으로 하드코딩되어 있어 차종을 추가할 때 번거롭다.

 

# AFTER

import string
import random

class VehicleInfo:
    
    def __init__(self, brand, electric, catalogue_price):
        self.brand = brand
        self.electric = electric
        self.catalogue_price = catalogue_price

    def compute_tax(self):
        tax_percentage = 0.05
        if self.electric:
            tax_percentage = 0.02
        return tax_percentage * self.catalogue_price

    def print(self):
        print(f"Brand: {self.brand}")
        print(f"Payable tax: {self.compute_tax()}")

class Vehicle:

    def __init__(self, id, license_plate, info):
        self.id = id
        self.license_plate = license_plate
        self.info = info

    def print(self):
        print(f"Id: {self.id}")
        print(f"License plate: {self.license_plate}")
        self.info.print()


class VehicleRegistry:

    def __init__(self):
        self.vehicle_info = { }
        self.add_vehicle_info("Tesla Model 3", True, 60000)
        self.add_vehicle_info("Volkswagen ID3", True, 35000)
        self.add_vehicle_info("BMW 5", False, 45000)
        self.add_vehicle_info("Tesla Model Y", True, 75000)

    def add_vehicle_info(self, brand, electric, catalogue_price):
        self.vehicle_info[brand] = VehicleInfo(brand, electric, catalogue_price)

    def generate_vehicle_id(self, length):
        return ''.join(random.choices(string.ascii_uppercase, k=length))

    def generate_vehicle_license(self, id):
        return f"{id[:2]}-{''.join(random.choices(string.digits, k=2))}-{''.join(random.choices(string.ascii_uppercase, k=2))}"

    def create_vehicle(self, brand):
        id = self.generate_vehicle_id(12)
        license_plate = self.generate_vehicle_license(id)
        return Vehicle(id, license_plate, self.vehicle_info[brand])


class Application:

    def register_vehicle(self, brand: string):
        # create a registry instance
        registry = VehicleRegistry()

        vehicle = registry.create_vehicle(brand)

        # print out the vehicle information
        vehicle.print()

app = Application()
app.register_vehicle("Volkswagen ID3")
  • VehicleInfo, Vehicle 클래스를 생성했다.
  • 각 클래스 내에 print 메소드를 만들었다.
  • 클래스와 직접적인 관련이 있고 다른 곳에서 사용할 일이 없는 함수들은 클래스 내에 위치한다. (high cohesion)
  • 예를 들면, compute_tax는 자동차 세금을 계산하는 하나의 일만 한다.

적절한 클래스 추가(VehicleInfo)로 리팩토링한 모습! 가독성과 유지보수성이 크게 향상되었다.

Summary

High cohesion, loose coupling을 지향하자.

 

🌟 내용에 오류가 있다면 댓글 달아주시면 감사하겠습니다.


References

https://www.youtube.com/watch?v=eiDyK_ofPPM&list=PLC0nd42SBTaNuP4iB4L6SJlMaHE71FG6N&index=1

https://github.com/ArjanCodes/betterpython

http://www.ktword.co.kr/test/view/view.php?m_temp1=2226 

반응형