해결된 질문
작성
·
18
·
수정됨
1
안녕하세요!
AbstractReader로부터 파싱에 대한 책임을 분리하여 Parser라는 클래스를 새로 생성하는 내용 잘 들었습니다!
여기서 궁금한 것이 생겨 질문 남깁니다..!
DatabaseReader, RedisReader, FileReader / CsvParser, JsonParser, XmlParser 구성은 총 9가지의 경우의 수가 나올 수 있을 것 같은데요
만약 '(DatabaseReader, XmlParser)는 불가능하고 (DatabaseReader, CsvParser)와 (DatabaseReader, JsonParser)가 가능하고,
(RedisReader, XmlParser)와 (RedisReader, CsvParser)는 불가능하고 (RedisReader, JsonParser)만 가능하다' 와 같이 특정 조합이 불가능한 경우는 어떻게 컴파일 타임에 가능한 조합으로만 강제할 수 있을지 궁금합니다!
답변 2
0
hello님 안녕하세요.
좋은 질문 남겨 주셔서 감사합니다. 🙂
컴파일타임에 의존성 주입 가능한 조합을 체크하는 방법 중에서 가장 간단한 방식은 파라미터의 타입을 명시적으로 드러내서 주입받을 수 있는 타입의 종류를 제한하는 것입니다.
간단하게 말해서 DatabaseReader와 RedisReade의 생성자에서 조합 가능한 Parser의 타입을 전달받는 생성자들을 추가하는 방식이죠.
(DatabaseReader, XmlParser)는 불가능하고 (DatabaseReader, CsvParser)와 (DatabaseReader, JsonParser)는 가능한 경우
public class DatabaseReader extends AbstractReader {
public DatabaseReader(String path, CsvParser parser) {
super(path, parser);
}
public DatabaseReader(String path, JsonParser parser) {
super(path, parser);
}
...
}
(RedisReader, XmlParser)와 (RedisReader, CsvParser)는 불가능하고 (RedisReader, JsonParser) 가능
public class RedisReader extends AbstractReader {
private JedisPool jedisPool;
public RedisReader(String path, JsonParser parser) {
super(path, parser);
}
public RedisReader(String path, JsonParser parser, JedisPool jedisPool) {
super(path, parser);
this.jedisPool = jedisPool;
}
}
생성자를 private으로 바꾸고 public static 생성 메서드를 추가해도 동일한 효과를 얻을 수 있습니다.
이 방식의 단점은 새롭게 조합 가능한 Parser를 추가하게 되면 Reader 들의 생성자를 함께 수정해야 한다는 점인데, 전체적인 복잡도 관점에서 보면 수용할만한 정도의 단점이라고 생각합니다.
만약 새로운 Parser가 추가됐을 때 Reader를 수정하고 싶지 않다면 마커 인터페이스(marker interface)를 사용하는 방법도 있습니다.
DatabaseReader와 조합 가능한 Parser 인터페이스를 제한하기 위해 Parser를 상속받는 DatabaseParsable 인터페이스를 추가합니다.
public interface DatabaseParsable extends Parser {}
RedisReader와 조합 가능한 Parser 인터페이스를 제한하기 위해 RedisParsable 인터페이스를 추가합니다.
public interface RedisParsable extends Parser {}
DatabaseReader는 DatabaseParsable만 조합할 수 있도록 제한합니다.
public class DatabaseReader<T extends DatabaseParsable> extends AbstractReader {
public DatabaseReader(String path, T parser) {
super(path, parser);
}
...
}
RedisReader는 RedisParsable만 조합할 수 있도록 제한합니다.
public class RedisReader<T extends RedisParsable> extends AbstractReader {
private JedisPool jedisPool;
public RedisReader(String path, T parser) {
super(path, parser);
}
public RedisReader(String path, T parser, JedisPool jedisPool) {
super(path, parser);
this.jedisPool = jedisPool;
}
...
}
DatabaseReader와 조합 가능하지만 RedisReader와는 조합할 수 없는 CsvParser는 DatabaseParsable 인터페이스만 구현합니다.
public class CsvParser implements DatabaseParsable {
...
}
DatabaseReader와 RedisReader 양쪽 모두와 조합할 수 있는 JsonParser는 DatabaseParsable와 RedisParsable 인터페이스 양쪽 모두를 구현합니다.
public class JsonParser implements DatabaseParsable, RedisParsable {
...
}
이 방식은 생성자를 이용하는 방식과 반대로 Parser가 추가될 때 Reader 계층 클래스를 수정할 필요가 없지만, Reader가 추가되면 조합 방식에 따라 Parser 계층 클래스를 수정해야 합니다.
개방-폐쇄 원칙 관점에서 Reader 계층과 Parser 계층 모두를 다 폐쇄시키는 어렵기 때문에 어떤 쪽의 더 중요한 변경의 축인지에 따라 적합한 방식을 선택하시면 될것 같아요.
클래스 계층이 너무 많아져서 이 제약을 클래스 계층 전반에 걸쳐 명시하는게 점점 복잡해진다면 컴파일타임이 아닌 런타임에 제한하는 쪽으로 구현하시는게 전체적인 복잡성을 낮추는 방법일 수 있습니다.
답변이 되었는지 모르겠네요. 😊
감사합니다.
0
안녕하세요, 인프런 AI 인턴입니다. hello님!
기존 설계에서 ‘Reader’와 ‘Parser’의 조합으로 총 9가지 경우의 수가 가능하다고 설명하셨고, 특정 조합이 불가능한 경우를 컴파일 타임에 강제하고 싶다고 하셨는데요, 이를 해결하기 위한 방법으로 몇 가지를 제안드리겠습니다.
가장 간단한 방법은 각 가능한 조합에 대해 구체적인 클래스를 생성하는 것입니다. 즉, 가능한 조합에 대해서만 해당하는 클래스를 명시적으로 만들어 주는 방식입니다. 이 방법은 경우의 수가 적을 때는 유효할 수 있지만, 경우의 수가 많아진다면 관리가 어려울 수 있습니다.
자바에서는 제너릭 타입과 제한된 다형성을 활용하여 가능 조합을 제한할 수 있습니다. 각 Reader가 특정 Parser를 받을 수 있도록 인터페이스 또는 추상 기능을 정의하는 것입니다.
// 인터페이스 정의
interface Parser {}
class CsvParser implements Parser {}
class JsonParser implements Parser {}
class XmlParser implements Parser {}
// 특정 Reader에만 적용 가능한 파서 조합 정의
interface DatabaseCompatibleParser extends Parser {}
class CsvDatabaseParser extends CsvParser implements DatabaseCompatibleParser {}
class JsonDatabaseParser extends JsonParser implements DatabaseCompatibleParser {}
interface RedisCompatibleParser extends Parser {}
class JsonRedisParser extends JsonParser implements RedisCompatibleParser {}
빌더 패턴을 활용하여 불가능한 조합을 방지할 수 있습니다. 빌더 내부에서 가능한 조합을 확인하거나 제한할 수 있게 로직을 추가합니다.
런타임에 검증함수를 사용하여 조합을 확인할 수도 있습니다. 다만, 이 방법은 컴파일 타임이 아닌 런타임에 오류를 확인하게 됩니다.
컴파일 타임 강제는 코드의 복잡도를 높일 수 있으므로, 유지보수성과 개발의 용이성을 위해 복잡도가 높은 경우 런타임 검증도 고려해볼 수 있습니다.
관련한 관점에서 Interface Segregation Principle를 적용하여 인터페이스를 잘게 나누는 것도 방법이 될 수 있습니다.
저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다. 현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏 추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.