본문 바로가기
📝 회고/✅ 22년 회고

[일일 회고] 22.01.13.목 - 초보의 OOP 설계와 구현기(다형성과 추상화, 정적 팩터리 메서드 적용)

by kukim 2022. 1. 13.

📚 개발 생각

오늘은 특정 주제에 대한 콘솔 프로젝트를 했다. 프로그램 실행부터 사용자 입, 출력과 데이터, 핵심 로직에 대한 요구사항이 있었다. 이를 절차 지향적으로 구현하는 것이 아니라 직접 OOP설계하고 구현하는 것이다. 예전에 C로 개발한 습관이 남아있어서 항상 어떤 문제를 볼 때 데이터를 중앙에 꺼내 쓰는 방식으로 개발했다. 이를 탈피하고자 특정 객체 안에 데이터와 프로시저로 묶어 정말 OOP 패러다임을 머릿속에 넣고 몸으로 익히고 싶었다. 사실 이전에 '객체지향의 사실과 오해 책'도 읽고 OOP 적용을 하려 했지만 역시나 어려웠다. 오늘의 회고는 프로젝트에서 겪었던 고민의문점을 남긴다.

 

OOP ..ing

✅  설계를 고민하고 구현하다 보니 다형성과 추상화가 된다고?

주어진 문제를 설계, 구현의 시간이 흘러 흘러, 갑자기 중간에 반복되는 클래스와 메서드가 많이 보였다. 이를 위해서 서로 다른 구현이었지만 추상화 과정을 거쳐보니 단 하나의 기능이었고 이를 묶고 싶었고 고민하다 보니 상위 타입에 대한 클래스 보이기 시작했다. 이를 위해 추상화하여 상위 클래스를 두었고 상속받아 하위 클래스의 필요 기능을 오버 라이딩하여 사용했다. 이때 추상화를 인터페이스로 할지 상속으로 할지 고민했다. 인터페이스는 "A를 할 수 있는"이라고 생각했고 상속은 "A이다."라고 생각했기 때문에 내 문제에서는 상속의 의미가 맞다고 생각해 상속으로 구현했다. 이를 통해 각 하위 클래스에 맞는 동일한 메서드를 사용할 수 있었고, 상위 클래스에 이름을 범용적으로 정하여 업 캐스팅하여 서브 클래스들의 호출에 강점을 두었다.

 

✅  이펙티브 자바 적용 : 정적 팩터리 메서드를 사용한 인스턴스 생성

정적 팩터리 메서드를 사용해 생성할 때 메서드 이름에 의미를 주어 사용자 입장에서 가독성을 주어 인스턴스 생성  시 파라미터 입력에 대한 어려움도 줄였다. 또한 객체 생성의 캡슐화를 주었고 상위 클래스에서 입력 파라미터에 따라 하위 자료형 객체를 반환하도록 만들어 하나의 정적 팩터리 메서드를 통해 편리함을 주었다.

 

아래 코드의 createFigureWithPoints() 정적 팩터리 메서드를 통해 points 매개변수에 따라 하위 객체인 Line, Triangle, Square, Polygon을 리턴한다. 의문점이 있다면 맨 아래 Main문을 보면 Square를 생성하는 방법이 3가지가 있다. 상위 객체 팩터리 메서드를 사용하는 방법, 하위 객체 팩터리 메서드 사용하는 방법, 생성자를 활용한 할당 방법이 있다. 문제가 있어 보이는데 어떤 것이 좋은지 모르겠다. 생각으로는 

public class Figure {
	// ...

	protected Figure(List<Point> points) {
        this.points = points;
        this.vertex = points.size();
    }

	public static Figure createFigureWithPoints(List<Point> points) {
        int vertex = points.size();

        switch (vertex) {
            case LINE_VERTEX:
                return new Line(points);
            case TRIANGLE_VERTEX:
                return new Triangle(points);
            case SQUARE_VERTEX:
                return new Square(points);
            default:
                return new Polygon(points);
        }
    }
    
	// ...    
}

public class Square extends Figure {

    public Square(List<Point> points) {
        super(points);
    }
    
    // ...
}


public class Main {
    public static void main(String[] args) {
        List<Point> points = init(); // points가 있다고 가정
        Figure Square1 = Figure.createFigureWithPoints(points);
        Figure Square2 = Square.createFigureWithPoints(points); 
        Figure Square3 = new Square(points); // 고민! Square 객체 생성하는 방법이 3가지나..
    }

✅  콘솔 프로젝트에 MVC 패턴 적용

예전에는 MVC 패턴은 웹에 한정이라고 생각했지만, 사실 MVC 패턴은 개발할 때 Model, View, Controller으로 나누어 개발하는 방법이기 때문에 위 패턴이 필요한 곳엔 어디든 적용이 가능하다. 김호돌님의 세상에서 제일 쉬운 MVC 패턴을 참고했다. 엔트리 포인트를 Application로 두었고, View에서 사용자 입력과 출력을 처리했고 Controller을 두어 Model과 View를 관리하였다. Model에 도메인을 넣고 작성하니 전체 프로젝트 구조가 생겼고 리팩터링과 확장성에 이점을 얻었다. 하지만 MVC도 만능이 아니기에. "은총알은 없다"를 기억하며.. (다음에 또 써봐야겠다)

 

✅  중요 도메인에만 단위 테스트 적용

저번주 단위 테스트를 적용하며 참패를 겪었기 때문일까?, 이번에는 도메인에 대해서만 단위 테스트를 작성했다. AAA 패턴을 적용해 테스트의 구조와 가독성을 높였고, 구현 세부사항별 테스트가 아닌 최종 결과, 로직의 동작에 대한 테스트를 하려 노력했다. 추상화되기 전 테스트는 변동이 많았지만, 추상화를 거친 후 테스트 코드는 최종 로직을 생각할 수 있었기 때문에 리팩터링 내성에 강했다. 좋은 테스트를 식별하고 작성하는 방법은 끝이 없기에 경험하고 즐겨보자.

 

 하드코딩 대신 상수

가독성과 유지보수를 위해 하드 코딩한 내용을 상수로 변경하였다.

 

👀 궁금증 1 : Q) 사용자 입력에 따라 예외 처리를 해야한다. 예외 처리를 어디서 해야 효율적일까?

두 가지 경우가 있다. 하나는 인스턴스 생성 전, 사용자에게 입력을 받고, 파라미터를 인스턴스에 넘기기 전에 예외 처리 메서드를 두어 앞단에서 끊는 방법, 다른 하나는 인스턴스 내부 생성할 때 입력 파라미터에 따라 생성할 수 없다고 예외처리를 던지는 방법으로 볼 수 있다. 프로젝트에서는 입력 예외처리 중 인스턴스 생성에 대한 범위는 객체가 가지고 있어야 유지보수가 좋을 거 같다고 생각해 두 번째 방법으로 구현했다. 예를 들어 "1,2,9" 란 사용자 입력이 있다고 했을 때 ", "(콤마) 단위로 파싱 한다면 1 / 2 / 3로 파싱 된다. 이를 가지고 인스턴스를 생성하면 된다. 하지만 인스턴스 생성 조건은 숫자 1 ~ 8이기 때문에 인스턴스 생성할 때 만들 수 없다고 예외처리를 던진다. 만약, 앞단에서 예외처리를 하는 경우는 "1,2, a"로 입력이 들어올 때 "a"는 아예 범위를 넘는 값이기 때문에 앞단에서 처리해야 한다고 생각했다. 무엇이 좋을까는 고민을 더 해봐야겠다.

 

👀 궁금증 2 : Q) 도메인 클래스에서 "계산을 위한 메서드"를 분리해야 하는가?

상-하위 구조의 많은 클래스에 동일한 멤버변수와 오버 라이딩된 계산 메서드가 있다. 사용할 때는 instance.calMethod()로 계산하여 그 결과를 리턴하고 있다. 이것을 계산 객체를 따로 두어, Calculate.calMethod(instance)로 처리하는 게 좋을지 고민이다. 현재는 계산 기능이 작기 때문에 확장성이 눈에 띄진 않지만, 만약 특정 로직이 많아진다면, 확장성을 고려해 분리하는 것도 좋다는 생각이 든다.


👍 Keep

  • 무작정 코딩 보다는 선 설계 후 구현하였다. 처음부터 완벽한 설계는 아니었지만 구현해가며 수정을 거듭했다.
  • 이펙티브 자바의 Item1을 적용하여 구현한 것에 뿌듯함을 느꼈다. 앞으로 남은 아이템도 하나씩 얻어보자
  • 팀원들과 설계에 대한 토론과 도메인 로직에 대한 팁을 얻어 어렵지 않았다.

🔥Problem

  • 집중하다 보니 운동과 중간에 쉬지 못함

🚒 Try

  • 집중하다보면 2~3시간이 훌쩍 지나가버리는데 꼭 1시간 단위로 쉬어야겠다.
  • 앞으로도 팀원들과 의견을 나누자
  • 배움을 멈추지 말자

댓글