- 다양한 주제에 대해 자유롭게 글을 작성하는 게시판입니다.
Date 24/02/08 08:05:07
Name   kaestro
Link #1   https://kaestro.github.io/%EA%B0%9C%EB%B0%9C%EC%9D%B4%EC%95%BC%EA%B8%B0/2024/02/07/%EC%B2%9C%EC%9B%90%EB%8F%8C%ED%8C%8C-%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%97%AD%EC%A0%84.html
Subject   천원돌파 의존성 역전
마크다운으로 작성한걸 html로 변환해주는거로 만들었더니 좀 생긴게 아쉽네요. 분명 제가 보여준곳에선 이정돈 아니었는데... 아쉽군요

지난번과 마찬가지로, 아쉬운 부분이나 문제있어서 고쳤으면 싶은 부분 말씀해주시면 큰 도움이 됩니다.

항상 감사드립니다




천원돌파 의존성 역전




목차



  1. 요약

  2. 문제 상황

  3. 의존성 역전을 통한 개선

  4. 다양한 방식의 의존성 역전

  5. 마치며




요약



  • 의존성 역전 패턴은 객체지향 프로그래밍의 핵심 원리 중 하나로, 객체 간의 결합도를 낮추고 유연성을 높이는 방법입니다.

  • 의존성 역전은 상위 모듈이 하위 모듈을 추상화한 인터페이스나 추상 클래스에 의존하도록 하는 것을 말합니다.

  • 제어 역전을 통해 필요한 의존 객체의 생성부터 사용, 생명주기 관리까지 모든 것을 외부에서 관리하도록 하는 것도 가능합니다.




문제 상황


실제로 이런 끔찍한 사례를 마주하지 않으면 더 좋겠지만, 만약 우리에게 다음과 같은 억지스러운 프로그램을 작성해야하는 상황이 닥쳤을 때를 한 번 생각해보겠습니다. 우리에게는 특정 바이너리 파일을 읽고 이 파일에 1이 얼마나 들어가 있는지 세는 프로그램을 만들라는 요구사항이 주어졌습니다. 그렇다면 다음과 같은 코드를 작성할 수도 있을 겁니다.


class BinaryFileManager:
    def open_file(self, file_name):
        self.file = open(file_name, 'rb')

    def read_file(self):
        return self.file.read().decode('utf-8', 'ignore')

class DataAnalyzer:
    def __init__(self):
        self.file_manager = BinaryFileManager()

    def process_data(self, file_name):
        self.file_manager.open_file(file_name)
        self.analyze_data()

    def analyze_data(self):
        data = self.file_manager.read_file()
        if data:
            ones_count = self.count_ones(data)
            print(f"Number of ones in the file: {ones_count}")
        else:
            print("No file open.")

    def count_ones(self, data):
        return data.count('1')

그런데 만약에 나중에 요구사항이 변화해서, 바이너리 파일에서 1의 갯수를 세는 것 만이 아니라 텍스트 파일에서 1의 갯수를 세는 것도 요구사항으로 추가된다면 어떻게 될까요? 어쩌면 우리는 다음과 같은 끔찍한 코드를 작성해야할 수도 있을겁니다.


class BinaryFileManager:
    def open_file(self, file_name):
        self.file = open(file_name, 'rb')

    def read_file(self):
        return self.file.read().decode('utf-8', 'ignore')

class TextFileManager:
    def open_file(self, file_name):
        self.file = open(file_name, 'r')

    def read_file(self):
        return self.file.read()

class DataAnalyzer:
    def __init__(self, file_type):
        if file_type == 'Binary':
            self.file_manager = BinaryFileManager()
        elif file_type == 'Text':
            self.file_manager = TextFileManager()

    def process_data(self, file_name):
        self.file_manager.open_file(file_name)
        self.analyze_data()

    def analyze_data(self):
        data = self.file_manager.read_file()
        if data:
            ones_count = self.count_ones(data)
            print(f"Number of ones in the file: {ones_count}")
        else:
            print("No file open.")

    def count_ones(self, data):
        return data.count('1')

우리는 단순히 DataAnalyzer 객체에서 사용할 새로운 클래스를 추가했을 뿐인데, DataAnalyzer 내부의 메서드 역시도 구현을 변환해야했습니다. 만약 이후에 새로운 파일 타입이 추가된다면, 또 다시 DataAnalyzer 내부의 메서드를 수정해야할 것입니다. 이런 상황에서 우리는 어떻게 해야할까요?




의존성 역전을 통한 개선


from abc import ABC, abstractmethod

class IFileManager(ABC):
    @abstractmethod
    def open_file(self, file_name):
        pass

    @abstractmethod
    def read_file(self):
        pass

class BinaryFileManager(IFileManager):
    def open_file(self, file_name):
        self.file = open(file_name, 'rb')

    def read_file(self):
        return self.file.read().decode('utf-8', 'ignore')

class TextFileManager(IFileManager):
    def open_file(self, file_name):
        self.file = open(file_name, 'r')

    def read_file(self):
        return self.file.read()

class DataAnalyzer:
    def __init__(self, file_manager: IFileManager):
        self.file_manager = file_manager

    def process_data(self, file_name):
        self.file_manager.open_file(file_name)
        self.analyze_data()

    def analyze_data(self):
        data = self.file_manager.read_file()
        if data:
            ones_count = self.count_ones(data)
            print(f"Number of ones in the file: {ones_count}")
        else:
            print("No file open.")

    def count_ones(self, data):
        return data.count('1')

개선한 코드는 Binary와 text 형태를 동시에 처리할 수 있게 사양이 변화됐는데도, 상위 모듈인 DataAnalyzer의 내부 구현은 영향을 받지 않았습니다. 이처럼 소프트웨어 간의 모듈이 상대에게 의존하게 될 때, 상대의 세부 구현이 변하더라도 상대적으로 적은 영향을 받도록 추구하는 패턴의 프로그래밍 작성 방식 중 하나를 우리는 의존성 역전이라 부릅니다.


의존성 역전은 구체적으로 상위 모듈이 하위 모듈에 의존하는 상황에서 그것의 구체적인 구현에 의존하지 않고, 하위 모듈을 추상화한 상위 모듈에 의존하도록 하는 것을 말합니다. 여기에서 말한 하위 모듈을 추상화한 상위 모듈이란 위의 예시에서 본 IFileManager와 같은 인터페이스, 혹은 추상 클래스들을 말합니다. 다른 예시를 들어 살펴보자면 컴퓨터와 주변 기기들을 다음과 같이 작성할 수 있을 것입니다.



import java.util.ArrayList;

interface PeripheralDevice {
    void connect();
}

class Mouse implements PeripheralDevice {
    @Override
    public void connect() {
        System.out.println("Mouse is connected.");
    }
}


class Keyboard implements PeripheralDevice {
    @Override
    public void connect() {
        System.out.println("Keyboard is connected.");
    }
}

class Computer {
    private ArrayList peripheralDevices;

    public Computer() {
        this.peripheralDevices = new ArrayList<>();
    }

    public void start() {
        System.out.println("Computer is starting...");
    }

    public void addPeripheral(PeripheralDevice peripheralDevice) {
        peripheralDevices.add(peripheralDevice);
    }

    public void connectPeripherals() {
        for (PeripheralDevice peripheralDevice : peripheralDevices) {
            peripheralDevice.connect();
        }
    }
}

public class Main {
    public static void main(String[] args) {
        // PeripheralDevice를 구현한 Mouse와 Keyboard 객체 생성
        PeripheralDevice mouse = new Mouse();
        PeripheralDevice keyboard = new Keyboard();

        // Computer 객체 생성 및 PeripheralDevice로 Mouse와 Keyboard 연결
        Computer desktop = new Computer();
        desktop.addPeripheral(mouse);
        desktop.addPeripheral(keyboard);

        // Computer 시작 및 PeripheralDevice 연결
        desktop.start();
        desktop.connectPeripherals();
    }
}

위에서 구현한 자바 코드는 컴퓨터를 OOP의 형태로 표현한 것입니다. 컴퓨터에는 다양한 주변기기를 연결할 수 있습니다. 그리고 그 주변기기의 동작에는 여러가지 형태가 존재하고, 앞으로도 새로운 주변기기가 생성될 것이니 interface라는 추상화된 상위 모듈에 구현을 의존한다면 더 유연하게 추가 구현이 발생할 때 대응할 수 있습니다.


의존성 역전 패턴을 활용하면 새로운 저수준의 모듈을 구현에 추가하려 할 때 뿐 아니라, 저수준 모듈의 구현이 변했을 때 이에 대응할 수 있다는 장점도 가지고 있습니다.


이제 예시를 통해 의존성 역전의 장점을 설명해보겠습니다.


기존에는 UserRepository 객체가 MySQL 데이터베이스에 저장하는 로직을 구현했다고 가정해봅시다. 그런데 MySQL이 유료화가 된다거나 심각한 보안 문제가 있는 것이 발견돼서 PostgreSQL 데이터베이스를 사용하도록 변경해야하는 상황을 맞이했습니다. 만약 기존 UserRepository가 다음과 같이 데이터베이스에 종속적이라면, 기존의 UserRepository 객체를 수정해야 할 것입니다.


public class UserRepository {
    public void save(User user) {
        // MySQL 데이터베이스에 저장하는 로직
        try {
            MysqlDataSource dataSource = new MysqlDataSource();
            dataSource.setUrl("jdbc:mysql://localhost:3306/mydatabase");
            dataSource.setUser("username");
            dataSource.setPassword("password");

            Connection connection = dataSource.getConnection();
            PreparedStatement statement = connection.prepareStatement("INSERT INTO users (username, email) VALUES (?, ?)");
            statement.setString(1, user.getUsername());
            statement.setString(2, user.getEmail());
            statement.executeUpdate();
            connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

public class UserRepository {
    public void save(User user) {
        // PostgreSQL 데이터베이스에 저장하는 로직
        try {
            PGSimpleDataSource dataSource = new PGSimpleDataSource();
            dataSource.setUrl("jdbc:postgresql://localhost:5432/mydatabase");
            dataSource.setUser("username");
            dataSource.setPassword("password");

            Connection connection = dataSource.getConnection();
            PreparedStatement statement = connection.prepareStatement("INSERT INTO users (username, email) VALUES (?, ?)");
            statement.setString(1, user.getUsername());
            statement.setString(2, user.getEmail());
            statement.executeUpdate();
            connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}


[위는 기존에 MySQL을 사용하다가 PostgreSQL을 사용하도록 변경한 코드입니다. 이렇게 변경하면 UserRepository 객체의 구현이 변경되어야 하므로 의존성 역전 패턴을 활용하지 않은 것입니다.]


하지만 의존성 역전 패턴을 활용하면 UserRepository 객체가 Database 인터페이스에 의존하도록 하고, MySQLDatabase와 PostgreSQLDatabase 클래스가 Database 인터페이스를 구현하도록 하면 UserRepository 객체는 Database 인터페이스에만 의존하게 되어 MySQL 데이터베이스를 사용하는 것이 아닌 PostgreSQL 데이터베이스를 사용하는 것으로 쉽게 변경할 수 있습니다.


public interface Database {
    void save(User user);
}

public class MySQLDatabase implements Database {
    @Override
    public void save(User user) {
        // MySQL 데이터베이스에 저장하는 로직
        try (Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydatabase", "username", "password")) {
            String query = "INSERT INTO users (username, email) VALUES (?, ?)";
            try (PreparedStatement preparedStatement = connection.prepareStatement(query)) {
                preparedStatement.setString(1, user.getUsername());
                preparedStatement.setString(2, user.getEmail());
                preparedStatement.executeUpdate();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

public class PostgreSQLDatabase implements Database {
    @Override
    public void save(User user) {
        // PostgreSQL 데이터베이스에 저장하는 로직
        try (Connection connection = DriverManager.getConnection("jdbc:postgresql://localhost:5432/mydatabase", "username", "password")) {
            String query = "INSERT INTO users (username, email) VALUES (?, ?)";
            try (PreparedStatement preparedStatement = connection.prepareStatement(query)) {
                preparedStatement.setString(1, user.getUsername());
                preparedStatement.setString(2, user.getEmail());
                preparedStatement.executeUpdate();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

public class UserRepository {
    private Database database;

    public UserRepository(Database database) {
        this.database = database; // 의존성 주입을 통해 Database 객체를 외부에서 받음
    }

    public void save(User user) {
        database.save(user);
    }
}

[위는 의존성 역전 패턴을 활용하여 MySQL을 사용하다가 PostgreSQL을 사용하도록 변경한 코드입니다. UserRepository 객체는 Database 인터페이스에만 의존하고 있으며, Database 인터페이스를 구현한 MySQLDatabase와 PostgreSQLDatabase 클래스가 Database 인터페이스를 구현하고 있습니다.]




다양한 방식의 의존성 역전


여태까지는 의존성 역전을 구현하는 방법으로 상위 모듈이 하위 모듈을 추상화한 인터페이스나 추상 클래스에 의존하도록 하는 방법을 살펴봤습니다. 그러나 제가 이야기하는 것은 추상화한 인터페이스나 클래스 도입에 따른 결합도 감소 및 유연성 높이는 방법이 아닌, 의존성 역전 패턴입니다. 그 이유는 이제 다음에 이야기할 추가적인 두가지 형태로 의존성 역전을 구현하는 것이 가능하기 때문입니다.



  • 의존성 주입

  • 제어 역전


의존성 주입


의존성 주입은 객체가 직접 자신이 사용할 객체를 생성하는 것이 아니라, 외부에서 객체를 주입받아 사용하는 방식을 말합니다. 이는 객체의 생성과 사용을 분리함으로써 객체의 재사용성을 높이고, 유연성을 높이는 장점이 있습니다. 의존성 주입은 다음과 같은 코드를 개선해야 하는 상황에서 사용할 수 있습니다.


class DataAnalyzer:
    def __init__(self):
        self.file_manager = BinaryFileManager()

    def process_data(self, file_name):
        self.file_manager.open_file(file_name)
        self.analyze_data()

    def analyze_data(self):
        data = self.file_manager.read_file()
        if data:
            ones_count = self.count_ones(data)
            print(f"Number of ones in the file: {ones_count}")
        else:
            print("No file open.")

    def count_ones(self, data):
        return data.count('1')

위의 코드에서 DataAnalyzer는 BinaryFileManager를 직접 생성하고 사용하고 있습니다. 이는 DataAnalyzer가 BinaryFileManager에 의존하고 있음을 의미합니다. 이를 의존성 주입을 통해 개선하면 다음과 같이 작성할 수 있습니다.


class DataAnalyzer:
    def __init__(self, file_manager):
        self.file_manager = file_manager

    def process_data(self, file_name):
        self.file_manager.open_file(file_name)
        self.analyze_data()

    def analyze_data(self):
        data = self.file_manager.read_file()
        if data:
            ones_count = self.count_ones(data)
            print(f"Number of ones in the file: {ones_count}")
        else:
            print("No file open.")

    def count_ones(self, data):
        return data.count('1')

의존성 주입을 통해 DataAnalyzer는 BinaryFileManager를 직접 생성하지 않고, 외부에서 주입받아 사용하고 있습니다. 이는 DataAnalyzer가 BinaryFileManager에 의존하지 않고, 외부에서 주입받은 객체에 의존하고 있음을 의미합니다.


그러나 의존성 주입은 객체의 생성과 사용을 분리함으로써 객체의 재사용성을 높이고, 유연성을 높이는 장점이 있지만, 객체를 생성하고 주입하는 코드가 복잡해질 수 있습니다.


이를 해결하기 위해 등장한 개념이 바로, 제어 역전입니다. 제어 역전은 객체의 생성과 사용을 분리함으로써 객체의 재사용성을 높이고, 유연성을 높이는 장점을 가지면서도, 객체를 생성하고 주입하는 코드가 복잡해지는 문제를 해결하기 위해 등장한 개념입니다.


제어 역전


제어 역전은 객체의 생성과 사용을 분리하기 위해 객체를 생성하고 사용하는 책임을 외부에 위임하는 것을 말합니다. 아까 전까지 이야기 한 의존성 주입과 얼핏 보면 동일한 이야기를 하는 것으로 이야기하기 쉽습니다. 하지만 의존성 주입은 객체를 생성하고 사용하는 책임을 외부에 위임하는 반면, 제어 역전은 객체를 생성하고 사용하는 책임을 객체 자신이 가지고 있는 것을 말합니다.


말이 좀 어렵죠? 집을 청소하는 상황을 비유를 들어서 한 번 이야기해보려 합니다. 집을 청소하는 상황에서, 집주인이 직접 청소를 하지 않고, 청소부에게 청소를 맡기는 것을 의존성 주입이라고 할 수 있습니다. 이 때 우리가 기존에 이야기 해 온 의존성 주입의 방식들은 집주인이 청소부에게 청소를 위임할 때, 필요한 도구들을 직접 전달하는 것과 같습니다.


그런데 이러기 위해서는 청소 도구들을 주인이 직접 관리해야할 뿐더러, 청소부가 어떤 도구를 사용해야 하는지에 대한 지시를 직접 해야하는 등의 문제가 있을 수 있습니다. 우리는 청소를 하기 싫어서 서비스를 이용하려는 것인데 오히려 더 많은 일을 해야하는 상황이 되는 것이죠.


그렇다면 아예 집안 청소 서비스를 이용한다면 어떻게 될까요? 우리는 단순히 서비스 업체를 이용하기만 하면 업체는 우리 집의 상황을 판단하고, 우리에게 필요한 모든 도구를 가져온 뒤에 청소를 해주는 것입니다. 이렇게 서비스를 이용하면 우리는 청소에 집중할 수 있고, 다른 것에 신경 쓰지 않아도 되는 것이죠.


그리고 이것이 바로 제어 역전입니다. 제어 역전은 우리가 맞이한 문제를 해결하기 위해 외부에 일을 맡기고, 그 일을 외부에서 해결하는 것을 말합니다. 마치 집안 청소 서비스를 이용하는 것처럼 말이죠.


비유를 통해 이야기해봤으니, 그러면 이제 코드를 통해 한 번 살펴보겠습니다. 예시로는 이제 우리에게 친숙해진 DataAnalyzer와 FileManager를 사용하겠습니다.


data_analyzer = DataAnalyzer(BinaryFileManager())
data_analyzer.process_data(sample_file_name)

만약 우리가 data_analyzer를 통해 BinaryFileManager를 사용하려 한다면, 위와 같은 코드를 사용하게 됩니다. 하지만 만약 기획이 바뀌어 우리에게 TextFileManager를 사용하라는 요구사항이 생긴다면, 우리는 다음과 같이 코드를 변경해야 할 것입니다.


data_analyzer = DataAnalyzer(TextFileManager())

이처럼 의존성 주입을 사용하고 있더라도, 기존의 방식으로는 여전히 객체를 생성하고 사용할 때 마다 문제 상황에 맞는 객체를 생성해야 하는 문제가 있습니다. 이런 현상을 이제 제어 역전을 사용하여 해결해보겠습니다.


class FileManagerController:
    def __init__(self):
        self.file_manager = None

    def get_file_manager(self):
        return self.file_manager

    def set_file_manager(self, file_type):
        if file_type == 'binary':
            self.file_manager = BinaryFileManager()
        elif file_type == 'text':
            self.file_manager = TextFileManager()

file_manager_controller = FileManagerController()
file_manager_controller.set_file_manager(filename.split('.')[-1])

data_analyzer = DataAnalyzer(file_manager_controller.get_file_manager())
data_analyzer.process_data(sample_file_name)

이를 통해 DataAnalyzer는 들어온 파일을 처리할 때 더 이상 파일의 정보를 알 필요가 없어졌습니다. 이는 파일 매니저 컨트롤러가 파일의 정보를 알고 있기 때문입니다. 이처럼 제어 역전을 사용하면 객체를 생성하고 사용할 때 마다 문제 상황에 맞는 객체를 생성해야 하는 문제를 해결할 수 있습니다.


다만 분명 FileManagerController가 들어와서 제어가 역전됐는데, 얼핏 보기에는 코드가 복잡해지고 있는 것처럼 보일 수 있습니다. 그러면 더 나아가서, 만약 우리가 이 데이터 분석 모듈을 웹서버에 탑재해야하는 경우를 상정해보면 어떨까요?


class WebServer:
    def __init__(self, data_analyzer):
        self.data_analyzer = data_analyzer

    def handle_request(self, file_name, file_type):
        self.file_manager_controller.set_file_manager(file_type)
        self.data_analyzer = DataAnalyzer(self.file_manager_controller.get_file_manager())
        self.data_analyzer.process_data(file_name)

이제 우리는 보내오는 파일이 어떤 형태이든지간에, FileManagerController만이 이것을 처리할 뿐 이를 이용하는 WebServer는 어떤 파일이 들어오든지간에, FileManagerController에게 파일의 형태를 알려주기만 하면 됩니다. 다음처럼 말입니다. 사실 조금만 더 손 보면 파일 형태도 FileManagerController에게 알려주지 않아도 될 것입니다.


file_manager_controller = FileManagerController()
web_server = WebServer(file_manager_controller)

web_server.handle_request("data.bin", 'binary')
web_server.handle_request("data.txt", 'text')



마치며


긴 여정을 통해 의존성 역전에 대해 알아보았습니다. 의존성 역전은 객체지향 프로그래밍의 핵심 원리 중 하나로 객체 간의 결합도를 낮추고 유연성을 높이는 방법으로, 잘 사용하면 코드의 재사용성을 높이고 유지보수성을 높일 수 있습니다. 하지만 집안에 쓰레기가 얼마 없을 때는 빗자루를 집어드는 것이 청소 업체에 연락을 하는 것보다 우선하듯이, 의존성 역전 역시 코드의 유연성을 높일 필요한 상황에서 사용하는 것이 중요합니다.


여러분이 앞으로 프로그램을 개발하는 데 있어, 그리고 제가 개발하는 과정에 있어서, 마주하게 될 많은 문제 상황들 중에 한번 쯤 의존성 역전이 이를 천원돌파하는 최강의 드릴이 되길 기원합니다. 그리고 이 글이 여러분의 그 드릴을 조금이나마 빛나게 해줄 수 있었다면 저에게는 더할나위 없는 즐거움이 될 것입니다. 긴 글 읽어주셔서 감사합니다. 다음 주제는... 뭐 당장 생각해 둔 것은 없습니다만 다른 디자인 패턴 중 하나를 써보면 어떨까 싶네요.





1


    집에 가는 제로스
    심리적인 의존성 역전이 아니었군요..? ㄷㄷ
    kaestro
    어쩐지 조회수는 올라가는데 다들 댓글이 없더라니...
    서포트벡터
    일단 파이썬을 알아야 글을 읽을 수 있으니까요...?
    1
    kaestro
    사실 중간에 자바도 섞은 근본없는 글입니다
    마크다운 지원 뷰를 만들어야겠군요. (349번째 할 일 목록에 추가하면서)
    1
    나의 pre 태그는 글 상자를 뚫는 태그이다!
    1. 의존성을 역전시키려면 사전에 계층이 명확히 존재해야합니다.
    글 대부분의 예제에는 상하위와 내외부가 혼재되어 있어 개념이 명확하지 않습니다.

    예제 속 데이터 분석기(DataAnalyzer)는 바이너리 분석기와 텍스트 분석기의 wrapper와 같은 역할을 하는데,
    이는 굳이 따지자면 Presentation층, 유저 인터페이스에 가까운 영역입니다. 유저가 데이터 분석기를 사용하니까요.

    utf8파일, 바이너리 파일이 가장 심층의 도메인 영역이라면,
    이를 이용하는 어플리케이션 층에 바이너리, 텍스트 분석기가 존재할... 더 보기
    1. 의존성을 역전시키려면 사전에 계층이 명확히 존재해야합니다.
    글 대부분의 예제에는 상하위와 내외부가 혼재되어 있어 개념이 명확하지 않습니다.

    예제 속 데이터 분석기(DataAnalyzer)는 바이너리 분석기와 텍스트 분석기의 wrapper와 같은 역할을 하는데,
    이는 굳이 따지자면 Presentation층, 유저 인터페이스에 가까운 영역입니다. 유저가 데이터 분석기를 사용하니까요.

    utf8파일, 바이너리 파일이 가장 심층의 도메인 영역이라면,
    이를 이용하는 어플리케이션 층에 바이너리, 텍스트 분석기가 존재할 것이고,
    가장 바깥 레이어에서 DataAnalyzer가 파일매니저 인터페이스를 통해 양측 분석기를 init하는 예제의 코드는
    오히려 의존성을 명확하게 지켰기 때문에 가능한 코드 간소화입니다.

    웹서버의 예시도 마찬가지입니다.
    오히려 의존성 역전이 발생하지 않았다고 볼 수 있죠.

    2. 역전으로 발생하는 메리트와 디메리트가 무엇입니까?
    의존성 역전을 허락하는 경우는 매우 드뭅니다.

    위의 예제에 언급하신 '파일'이라는 객체를, 내가 만들 프로그램 위에서 도메인화 하는 작업을 한다면...
    파일의 props와 type을 작성합니다. 바이너리로 할지, 확장자는 뭐로 제한 하고 문자코드는 뭘로 제한할지... 그렇게 해서 데이터 형을 도메인화 시켜둘겁니다.
    그렇게 하면 유저 인터페이스를 통해 타고 들어오는 데이터가 도메인이 지정한 영역 안의 객체임을 명확히 할 수 있습니다.
    이렇게 만든 의존성이 지켜진 상태에서 애플리케이션 코드 작성은 예외를 생각하지 않아도 되기 때문에 한결 편해집니다.

    의존성 역전은... 이 구조를 붕괴시킵니다. 외부의 변화에 도메인이 바뀌고, 도메인이 바뀌는 것을 대응해 애플리케이션층, 프레젠테이션층의 코드가 흔들립니다.
    이러한 디메리트를 감수하며 역전시킨 코드의 메리트와 디메리트는 무엇입니까?

    제 기준에서 의존성 역전은 '어쩔 수 없을 때' 하는 겁니다.
    예를들어서 타사의 API사양에 의존하는 경우가 그렇습니다.

    파일의 확장자 제한을 'txt'로 했는데, 써드파티앱에서json형식을 허가했다고 칩시다.
    여기서 의존성을 완벽하게 지킨 프로그램은, stable한 버전의 api사양을 가져와서 도메인화합니다. 업데이트가 있으면 수동으로 심층부터 고쳐나갑니다.

    하지만 요새는 패키지화되어 있는 api형 정보를 사용하는 방식을 씁니다. (요새는 api사양이 각 언어별로 type화되어 배포합니다)
    도메인층이 외부의 패키지의 데이터형을 사용하도록 하고, 그에 알맞게 애플리케이션층의 파일 매니저에 미리 try/catch화합니다.
    써드파티 앱이 json을 지원하게 되어, 유저가 json파일을 업로드했을때, 당장 내 앱이 붕괴되지 않도록 캐치할 정도로만 써두는거죠.

    이런 부분적인 것을 허가하기 위해 이론화 한것이 '의존성 역전'입니다.
    코드의 효율화만으로는 설명할 수 없는 영역이기 때문에, 비즈니스모델을 이해하고 코드 구현과 유지보수의 비용을 저울질할 필요가 있습니다.

    의존성 역전 자체를 설명하는 것은 한줄로 됩니다.
    '도메인 층과 같은 심부 영역이 외부에 의지할 수 밖에 없을 때, 조금 더 유연하게 대처할 수 있는 설계의 궁리'입니다.

    근데 이를 설명하기 위해서는 의존성이 왜 팀개발에 중요한지, 이를 파괴하면 어떤 디메리트가 있는지 알려주셔야 합니다.
    그렇지 않으면 '이 글의 예제 속에서 사용된 전제'만으로는 역전할 이유가 굳이 없기 때문입니다.

    건축도면만 있다고 어느 땅에나 지을 수 없는 것처럼, 설계에는 반드시 그 비즈니스 모델에 대한 이해가 필요합니다.
    그렇기 때문에 case by case입니다. 수식처럼 공식화할 수가 없는 것입니다.

    저는 선생님의 의존성 역전에 대한 이해가 완전히 틀렸다고 생각하진 않습니다만,
    이를 제대로 이용하고 설명하려면 더 기본적인 이해, 코드 밖에서 코드를 읽는 방법이 뒷받침되어야 가능하지 않을까라고 조심스레 말씀드립니다.
    1
    kaestro
    거의 제가 쓴 글만큼 리뷰해주시니 생각해볼게 많네요. 모자란 글을 신경써서 읽어주시니 그저 감사할 뿐입니다.

    말씀대로 글의 예제만 가지고 역전할 이유가 굳이 없고, 코드 밖에서 읽는게 뒷받침 되니 전에 이야기하신 코드를 써보고 읽는게 더 우선해야한다는게 무슨 말씀이신지 어렴풋이 알 것 같습니다.

    이런 부분까지 고려해서 이에 대해 공부하려면 아무래도 제가 예시를 만들어보는 것보다는 이것을 잘 활용한 소스코드를 읽어보는 것이 더 좋을 것 같네요

    팀 협업은 제가 경험이 아무래도 거의 없다고 봐야하는 것이 맞아서, 말씀하신... 더 보기
    거의 제가 쓴 글만큼 리뷰해주시니 생각해볼게 많네요. 모자란 글을 신경써서 읽어주시니 그저 감사할 뿐입니다.

    말씀대로 글의 예제만 가지고 역전할 이유가 굳이 없고, 코드 밖에서 읽는게 뒷받침 되니 전에 이야기하신 코드를 써보고 읽는게 더 우선해야한다는게 무슨 말씀이신지 어렴풋이 알 것 같습니다.

    이런 부분까지 고려해서 이에 대해 공부하려면 아무래도 제가 예시를 만들어보는 것보다는 이것을 잘 활용한 소스코드를 읽어보는 것이 더 좋을 것 같네요

    팀 협업은 제가 경험이 아무래도 거의 없다고 봐야하는 것이 맞아서, 말씀하신 그런 부분에서 상상이 아예 안 되는 것이 맞아 이쪽 부분보다는 조금 더 쉬운 부분들에 대해 다루면서 공부를 선행하는 것이 맞을 것 같다는 생각도 드네요.

    말씀해주신 부분에 대해서 시간이 지나 다시 한 번 더 읽어보고, 혹시 질문이 생기면 드려보고 싶네요. 사실 제가 이해하기에는 아직은 경험이 모자라서 안 닿는 부분이 많아서. 그래도 이런 이야기를 들으면서 경험 많은 분의 이야기를 듣는 게 제가 글을 쓰는 가장 큰 이유긴 합니다.

    전에도 그렇고 이번에도 많은 도움이 되는 이야기를 해주셔서 감사합니다.

    가능하면 조심스레가 아니라, 더 비판적인 표현을 한다면 어떤 이야기를 하실 수 있을지도 궁금하고 지난번과 비교해서는 이번 글은 좀 어떤 것 같다고 느끼시는지도 궁금하네요. 저는 지난번보다는 낫지 않나...?라는 생각 정도는 했었거든요
    설계에 대한 글은 구루들도 잘 안씁니다.
    그들이 뭔가 틀린 글을 써서가 아닙니다. 왜냐면 설계는 대부분 아는만큼 보이기 때문에 대부분의 독자층이... 어쩔수 없이 쇠귀에 경읽기가 됩니다.
    이해했다고, 좋은 책이라는 사람들도 어디까지 이해했는지 알수가 없습니다. 그들만의 서클처럼 되서 보기 숭하죠. 그래서 안씁니다.

    대부분의 설계책들은 낡고 낡았습니다. 엉클밥의 클린 코드가 나온 08년에도 원론적인 이야기라며 겁나게 두드려맞았습니다.
    그 08년부터 지금까지 수도 없이 새로운 설계들이 나왔지만, 대부분 클린 코드의 테제 혹은... 더 보기
    설계에 대한 글은 구루들도 잘 안씁니다.
    그들이 뭔가 틀린 글을 써서가 아닙니다. 왜냐면 설계는 대부분 아는만큼 보이기 때문에 대부분의 독자층이... 어쩔수 없이 쇠귀에 경읽기가 됩니다.
    이해했다고, 좋은 책이라는 사람들도 어디까지 이해했는지 알수가 없습니다. 그들만의 서클처럼 되서 보기 숭하죠. 그래서 안씁니다.

    대부분의 설계책들은 낡고 낡았습니다. 엉클밥의 클린 코드가 나온 08년에도 원론적인 이야기라며 겁나게 두드려맞았습니다.
    그 08년부터 지금까지 수도 없이 새로운 설계들이 나왔지만, 대부분 클린 코드의 테제 혹은 안티테제였습니다.
    (그렇다고해서 클린 코드가 좋은 책이라는 것도 아닙니다. 당연한 말을 잘 쓰는 것도 어렵다는 이야기입니다.)

    의존성 역전도 클린코드의 연장선이나 마찬가지입니다.
    의존성이 왜 중요한지 클린코드에 써있으니까요.

    저는 글에 대해서는 잘 모르고, 다른 개발 글타래도 많이 읽지 않기 때문에 수준에 대해 코멘트 드리기는 어렵습니다.
    부두교에 너무 심취하지 마세요. 여러번 말씀드리지만 실제로 동작하는 코드에 사용한 설계보다 더 좋은 글은 없습니다.
    1
    아, 지난번 글보다는 예제가 있어서인지, 어떻게 이해하고 계신지 투명히 보인건 있었습니다 ㅋㅋ
    그래서 댓글에 어떤 답변을 하면 되겠다는 떠올리기 편했어요.
    1
    kaestro
    확실히 이렇게 자기 머리 속에 있는 이해를 가지고 예제를 끄집어내는 것 자체는 꽤 재밌는 경험이긴 했습니다ㅋㅋ

    이걸 하려고하니까 결국 내가 어떤 상황에서, 어떤 목적을 위해 이 개념을 적용해서 어떤 형태의 것을 만들어야겠다라는 게 결정이 돼야하니까 그런 과정 자체는 꽤나 저는 마음에 들었거든요. 그래봤자 그 근간에 있는 경험 자체가 아직 많이 모자라고, 그래서 이해가 모자란 내용으로 가득한게 됐습니다만, 그 과정 자체에 의미를 두는 것은 좋겠다는 생각이 들었습니다.

    뭐 그리고 이렇게 틀린 소리라도, 자기 생각을 명확하게 ... 더 보기
    확실히 이렇게 자기 머리 속에 있는 이해를 가지고 예제를 끄집어내는 것 자체는 꽤 재밌는 경험이긴 했습니다ㅋㅋ

    이걸 하려고하니까 결국 내가 어떤 상황에서, 어떤 목적을 위해 이 개념을 적용해서 어떤 형태의 것을 만들어야겠다라는 게 결정이 돼야하니까 그런 과정 자체는 꽤나 저는 마음에 들었거든요. 그래봤자 그 근간에 있는 경험 자체가 아직 많이 모자라고, 그래서 이해가 모자란 내용으로 가득한게 됐습니다만, 그 과정 자체에 의미를 두는 것은 좋겠다는 생각이 들었습니다.

    뭐 그리고 이렇게 틀린 소리라도, 자기 생각을 명확하게 표현할 줄 아는 것 자체도 중요한 능력일테고?말이죠

    별개로 설계에 대한 글을 쓰려면 아무리 욕먹어도 클린코드 정도는 읽어보긴 해야겠다는 생각은 드네요

    그런데 그런 글 타래도 안 읽어보시는 분께서 제 글은 읽어주신게 뭔가 좀 죄송스럽기도 하고 감사하군요ㅋㅋ
    아뇨 저도 재밌었습니다.

    이렇게 과정을 즐기시면서 배움이 있으시다면 그거도 좋죠.

    더 비판적인 표현을 한다면 어떤 이야기를 할 수 있을지도 궁금하다고 하셨는데
    근데 여기서 더 신랄하게 비판을 받으셔도 즐기실 수 있겠습니까..?
    일단 제가 재미없을 것 같습니다.

    주화입마에서 벗어나는 방법은 그냥 손을 놓는 것입니다.
    놓는 것도 용기가 필요한 행동입니다. 이해가 확실히 안되었다고 판단이 된다면 그냥 부분을 드랍하세요.
    클린 코드도 회사들어가셔서 남의 코드를 한 1년 정도 충분히 읽어보시고 판단하세요.
    지금은... 더 보기
    아뇨 저도 재밌었습니다.

    이렇게 과정을 즐기시면서 배움이 있으시다면 그거도 좋죠.

    더 비판적인 표현을 한다면 어떤 이야기를 할 수 있을지도 궁금하다고 하셨는데
    근데 여기서 더 신랄하게 비판을 받으셔도 즐기실 수 있겠습니까..?
    일단 제가 재미없을 것 같습니다.

    주화입마에서 벗어나는 방법은 그냥 손을 놓는 것입니다.
    놓는 것도 용기가 필요한 행동입니다. 이해가 확실히 안되었다고 판단이 된다면 그냥 부분을 드랍하세요.
    클린 코드도 회사들어가셔서 남의 코드를 한 1년 정도 충분히 읽어보시고 판단하세요.
    지금은 불난 집에 기름만 더 부을 뿐이라고 생각합니다.

    설계 레벨의 지식이 필요한 때가 반드시 찾아오지만, 솔직히 설계는 머리 한 두명이 하는 회사가 대다수입니다.
    토론하고 팀원들과 결정하려 해도, 결국 테크리드말대로 합니다. 다들 자신 없어서 쓰라는대로 씁니다...
    시키는대로 잘해야 빠르게 습득하기도 합니다. 펠로우쉽이라고도 하죠...
    기초 체력 키우듯 알고리즘과 문제해결 위주로 공부하십시오.
    2
    kaestro
    ㅋㅋㅋ

    항상 뼈와 살이 되는 이야기 감사드립니다

    말씀대로 그건 재미없는 일이 될 것 같다는 생각이 드네요.

    조급해하지 말고 숲을 보려하기 보다 일단 눈앞에 있는 나무나 잘 패는 연습해야겠습니다
    훌륭한 각오십니다.
    kaestro
    그리고, 사실 제가 자료 찾아보면서는 말씀하신 이런 생각을 전혀 하지 못했고 그냥 어렴풋이 '오 뭔가 대단해보이는데!' 생각 밖에 못했거든요.

    또 전에 '이런 글 쓰느니 코드나 한 줄 더...'라고 해주시면서 이런거 해보라 하셨던 부분을 따라서, 요즘 매일 leetcode, medium을 통해 코드 짜는 것부터 시작해서 다양하게 작성해보려고 노력하는 중 입니다. 그리고 많은 도움이 되고 있다고 느끼고 있어요.

    그거 외에는 회사들에서 과제 제출용으로 웹서버 개발 등을 요청하는 것이 있어서 일단 경험을 통해 제가 하고 싶은 프로젝트가 뭔지 궁리해보는 중입니다.

    다시 한 번 정말 감사드립니다
    1
    원금복구제발ㅠㅠ
    의존성 역전을 포함해서 다른 OOP개념, 설계 개념 자체가 어려운건 아닌데 처음 배우는 분들(아마도 학생들)은 막상 받아들이기 어려워하는경우가 종종 있습니다.
    이런 개념을 블로그등을 통해 공부하시는 분들에게는 대체로 실전성이 있는 코드가 예제로 제공되지 않기 때문에 그런것 같습니다.
    (실전성 있는 코드를 예로 들려면 작성자가 고통이겠죠 ㅎ)

    실제로, 한번 구현하면 코드가 그렇게 바뀌지도 않는데 왜 이걸 써야하냐? 를 시작으로 여러 질문들이 많이 나옵니다.
    테스트코드를 딥하게 작성해본적도 없고, 라이브러리를 개발하면서, 사... 더 보기
    의존성 역전을 포함해서 다른 OOP개념, 설계 개념 자체가 어려운건 아닌데 처음 배우는 분들(아마도 학생들)은 막상 받아들이기 어려워하는경우가 종종 있습니다.
    이런 개념을 블로그등을 통해 공부하시는 분들에게는 대체로 실전성이 있는 코드가 예제로 제공되지 않기 때문에 그런것 같습니다.
    (실전성 있는 코드를 예로 들려면 작성자가 고통이겠죠 ㅎ)

    실제로, 한번 구현하면 코드가 그렇게 바뀌지도 않는데 왜 이걸 써야하냐? 를 시작으로 여러 질문들이 많이 나옵니다.
    테스트코드를 딥하게 작성해본적도 없고, 라이브러리를 개발하면서, 사용자에게 틀만 제공해주고, 세부 구현에 대해선 외부에 맡겨야하는 상황을 직접 겪어보지 않으면 와닿지 않습니다.

    만약에 그런 지식이 필요한 시기가 아닌데, 책을 보면서 Foo, Bar.. 뭐 이런 클래스를 예제로 보면서 배우고자 하면.. 눈이 핑핑 돌아갑니다.
    바로 이해가 안된다면, 아직 그걸 배울 시기가 아닐수도 있습니다.

    위에 얘기했듯, 어렴풋이 뭔가 있는것 같은데 손에 안잡힐 때, 해보려고 하는데 잘 안될 때, 실제 그런게 필요한 상황을 마주쳤을 때.
    그럴땐 그냥 만화책 읽듯, 머릿속에 술술 들어옵니다.

    주니어분들에게는 어느정도 고속성장할 수 있는 방법이 있긴 한것 같습니다.
    최대한 실전에서 개발 경험치를 먹고, 동료들의 결과물 역시도 많이 들여다 봐야 합니다.
    그리고 나보다 무조건 역량이 한없이 위에 있는 동료들과 일하면 됩니다.
    동료들의 높은 실력에 좌절하지 않을 용기와, 끊임없이 배울 자세가 있으면 2-3년 안에 끝없이 레벨업 하는 내가 보입니다.
    kaestro
    격려 감사드립니다ㅋㅋ
    말씀대로 경험이 미천하다는 인상과 아직 이걸 할 때가 아니란 생각이 들어, 조금 더 쉬운 것부터 시작해보려하고 있습니다
    어제부터 head first design pattern 보고 있는데, 책이 꽤 괜찮더라구요
    워낙에 두꺼운 책이니 이번 달은 이것 하나 천천히 음미하면서 진행해보려고 합니다
    목록
    번호 제목 이름 날짜 조회 추천
    15289 IT/컴퓨터클로드 3.7에게 소설을 맡겨보았다 - 물망초의 기억 유하 25/03/01 631 3
    15236 IT/컴퓨터결국 구입해버린 저지연코덱 지원 헤드셋 8 야얌 25/01/27 891 0
    15210 IT/컴퓨터금융인증서와 공동인증서 4 달씨 25/01/15 2546 0
    15188 IT/컴퓨터인공지능 시대, 우리에게 필요한 것은 "말빨" 4 T.Robin 25/01/05 1100 7
    15157 IT/컴퓨터AI가 점점 무서워지고 있습니다. 7 제그리드 24/12/26 1211 0
    15125 IT/컴퓨터모니터 대신 메타 퀘스트3 VR 써보기(업데이트) 10 바쿠 24/12/12 1650 5
    15081 IT/컴퓨터분류를 잘하면 중간은 간다..? 닭장군 24/12/01 925 5
    15032 IT/컴퓨터추천 버튼을 누르면 어떻게 되나 13 토비 24/11/08 1306 35
    15010 IT/컴퓨터[마감] 애플원(아이클라우드 + 애플뮤직+...) + 아이클라우드 2TB 파티원 모집 중! (6/6) 20 아란 24/10/30 1344 0
    14885 IT/컴퓨터도시의 심연 (서울 싱크홀 모티브의 창작소설) 1 타는저녁놀 24/09/01 1055 1
    14871 IT/컴퓨터호텔방 카드키의 사실상 표준인 mifare에서 하드웨어적 백도어 발견 7 보리건빵 24/08/27 1679 0
    14867 IT/컴퓨터카드사 스크래핑을 해볼까? 3 삼성그룹 24/08/26 1536 5
    14769 IT/컴퓨터독한 랜섬웨어에 걸렸습니다 5 블리츠 24/07/02 1691 0
    14737 IT/컴퓨터애플의 쓸대없는 고집에서 시작된 아이패드 계산기 업데이트 8 Leeka 24/06/11 2482 0
    14734 IT/컴퓨터인공지능과 개발자 12 제그리드 24/06/10 1991 5
    14680 IT/컴퓨터Life hack : 내가 사용하는 도구들 2 Jargon 24/05/14 1876 5
    14676 IT/컴퓨터BING AI 에서 노래도 만들어주네요.. 3 soulless 24/05/14 1277 0
    14670 IT/컴퓨터인체공학을 염두에 둔 내 pc용 책상 세팅(1) 23 kaestro 24/05/12 1692 2
    14622 IT/컴퓨터5년후 2029년의 애플과 구글 3 아침커피 24/04/25 1776 1
    14614 IT/컴퓨터re: 제로부터 시작하는 기술 블로그(1) 2 kaestro 24/04/22 1515 1
    14473 IT/컴퓨터유부남의 몰래 [PC처분]-판매완료 17 방사능홍차 24/02/20 3404 0
    14442 IT/컴퓨터천원돌파 의존성 역전 17 kaestro 24/02/08 4114 1
    14422 IT/컴퓨터의존성 역전 패턴을 활용한 소프트웨어 설계 개선(1~3) 30 kaestro 24/01/30 2545 0
    14383 IT/컴퓨터구글에 암호를 모두 저장하는 습관 36 매뉴물있뉴 24/01/05 3501 1
    14259 IT/컴퓨터잠시 마법세계 다녀오겠습니다?? 1 큐리스 23/11/06 2780 1
    목록

    + : 최근 2시간내에 달린 댓글
    + : 최근 4시간내에 달린 댓글

    댓글
    회원정보 보기
    닫기
    회원정보 보기
    닫기
    회원정보 보기
    닫기
    회원정보 보기
    닫기
    회원정보 보기
    닫기
    회원정보 보기
    닫기
    회원정보 보기
    닫기
    회원정보 보기
    닫기
    회원정보 보기
    닫기
    회원정보 보기
    닫기
    회원정보 보기
    닫기
    회원정보 보기
    닫기
    회원정보 보기
    닫기
    회원정보 보기
    닫기
    회원정보 보기
    닫기
    회원정보 보기
    닫기
    회원정보 보기
    닫기
    회원정보 보기
    닫기
    회원정보 보기
    닫기
    회원정보 보기
    닫기
    회원정보 보기
    닫기
    회원정보 보기
    닫기
    회원정보 보기
    닫기
    회원정보 보기
    닫기
    회원정보 보기
    닫기
    회원정보 보기
    닫기
    회원정보 보기
    닫기
    회원정보 보기
    닫기
    회원정보 보기
    닫기
    회원정보 보기
    닫기
    회원정보 보기
    닫기
    회원정보 보기
    닫기
    회원정보 보기
    닫기
    회원정보 보기
    닫기
    회원정보 보기
    닫기
    회원정보 보기
    닫기
    회원정보 보기
    닫기
    회원정보 보기
    닫기
    회원정보 보기
    닫기
    회원정보 보기
    닫기
    회원정보 보기
    닫기
    회원정보 보기
    닫기
    회원정보 보기
    닫기