클린코드 6장. 객체와 자료구조

600g (Kim Dong Geun)·2021년 8월 22일
0

클린코드 6. 객체와 자료구조

캡슐화 하는 이유 : 남들이 변수에 의존하지 않고, 추상화된 메소드만을 통해서 프로그램을 서술해 나가도록 할 수 있음

그래서 캡슐화를 왜 해야하는지 그럼, 무작정 캡슐화를 하면 되는걸까 라고 의문점을 던지는 것으로 시작해서 객체지향적인 풀이법이 마냥 해답은 아니다. 어떤 패러다임이나 방식으로도 유연한 설계를 구현할 수 있다면 최선의 해결책을 선택해라 라는걸 전반적인 주제

자료추상화

//자료 6-1 
public class Point{
  public double x;
  public double y;
}
//자료 6-2
public interface Point {
  double getX();
  double getY();
  void setCartesian(double x, double y);
  double getR();
  double getTheta();
  void setPolar(double r, double theta);
}
  • 6-2 에서는 어떤 좌표계를 사용하는지 알 길이 없다.

  • 그러나 6-2에서는 자료 구조를 명백하게 표현한다.

  • 6-1 에서는 확실히 직교 좌표계를 사용함을 알 수 있다
    • x,y를 변수로 가지는것을 확실히 볼 수 있으니까?

근데 적절한 예제라기 보다는 설명을 위한 예제라는 느낌을 받았고.

개인적인 생각으로는 메소드를 위와 같이 정의했다라는 것은 2가지 기능 다 구현을 해야 한다 생각이 들고UnSupportedOperationException 를 써야 한다면, 인터페이스를 분리 하는게 맞지 않을까 라는 생각

  • 6-1 에서 변수를 private으로 선언하고 조회 함수와 설정함수를 제공한다면 구현을 외부로 노출하는 셈이다.
  • 변수사이에 함수라는 계층을 넣는다고 구현이 저절로 감춰지지 않는다.
  • 추상 인터페이스를 제공해서 사용자가 구현을 모른 채 자료의 핵심을 조작할 수 있어야 진정한의미의 클래스이다.

결국 이 로버트 마틴 C 가 말하고자 하는 부분은

정의와 구현을 하나의 클래스로 두면, 개발자 입장에서는 결국 그 기능을 알기 위해서는 코드까지 들어가봐야 하고 그러다보면 생산성이 떨어질 수도 있다라는걸 말하는게 아닌가 생각이 들었고,

이러한 불편함을 해결하기 위해서 추상인터페이스와 그것을 구현한 클래스를 둠으로써 정의부와 구현부를 분리하는 브릿지 패턴을 사용하라 라는 느낌을 받았다.

그렇게 분리를 해두면 추상인터페이스만 보고도 어떻게 동작하고, 어떤 객체를 리턴하는지 확인할 수 있으니까?

브릿지 패턴 링크

//6-3 구체적인 Vehicle 클래스
public interface Vehicle {
  double getFuelTankCapacityInGallons();
  double getGallonsOfGasoline();
}

//6-4 추상적인 Vehicle 클래스
public interface Vehicle {
  double getPercentFuelRemaining();
}
  • 캡슐화를 할때에도 단순히 특정 클래스에 종속되는 변수이름을 넣어서 함수로 만들지 말고, 약간의 모호함, 추상적인 개념을 넣어서 어떤 클래스에도 적용할 수 있게끔 설계하라 라는 인상을 받음.

이거는 여기에 대해서 할말은 딱히 없음. 대신 설명을 위한 ㅇㅖ제라는 느낌을 받음 사실 6-3도 별로 이상할 것 없는 느낌..?

자료 객체 / 비대칭

public class Square {
  public Point topLeft;
  public double side;
}

public class Rectangle {
  public Point topLeft;
  public double height;
  public double width;
}

public class Circle {
  public Point center;
  public double radius;
}

public class Geometry {
  public final double PI = 3.14;
  
  public double area(Object shape) throws NoSuchShapeException{
    if(shape instanceof Square){
      Square s = (Suare)shape;
      return s.side * s.side;
    }else if (shape instanceof Rectangle){
      Rectangle r = (Rectangle) shape;
      return r.height * r.width;
    }else if (shape instanceof Circe){
      Circle c = (Circle)shape;
     	return PI * c.radius * c.radius; 
    }
      throw new NoSuchShapeException();
    }
	}

}
  • 코드에 대해 요약하자면, Geometry.area() 함수가 객체가 어떤 도형 클래스인지 판별하고 그 도형에 맞는 넓이 공식을 적용후 리턴해주는 코드
  • 절차 지향적인 느낌이 강하다라고 서술하고 있음.
  • 그리고 위 코드가 절차지향적인 부분으로 본다면 나쁜 코드는 아니라고 볼 수 있음, 오히려 함수의 추가가 좀더 용이롭다 라고 말하고 있음
public class Square implements Shape {
  private Point topLeft;
  private double side;
  
  public double area() {
    return side * side;
  }
}

public class Rectangle implements Shape {
  private Point topLeft;
  private double height;
  private double width;
  
  public double area(){
    return height * width;
  }
}
  • 반면 위 코드는 Shape를 상속받은 각각의 도형들이 area 함수를 구현하는 모습
  • 객체지향적인 부분이 강하다고 말하고 있음
  • 클래스를 추가하기 쉬움

그리고 이 둘이 근본적으로 책에서 어떤 차이점이 있냐고 말하면

  • 절차 지향 같은 경우는 기존 자료 구조를 변경하지 않으면서 새 함수를 추가하기 쉽다.
  • 객체 지향 같은 경우는 기존 함수를 변경하지 않으면서 새 클래스를 추가하기 쉽다

절차 지향 같은 경우는 함수 추가 하고, 지원하지 않는 부분에 대해서 NoSuchShapeException 을 때리면 되니까

클래스 같은 경우는 함수를 추가하는데 있어, 상속받는 클래스들에 대해서 모조리 구현을 추가하면 되니까

물론 상속받는 클래스들에 대해서 모조리 구현을 추가하지 않고 해결하는 방법으로 Visitor 패턴이 있다고함

public class ShapeVisitor {
  
  private constrouctor(){
    
  }
  
  public static double area(Rectangle rectangle){
    return rectangle.getHeight() * rectangle.getWidth();
  }
  
  public static double area(Square square){
    return square.getSide() * square.getSide();
  }
  
  //만약 상속받는 클래스가 한개 더 생기면 여기에다 그대로 정의해주고 사용만 하면 되니까~~
  public static double area(임의의 클래스 R){
    throw new UnSupportedOperationException();
  }
}

디미터 법칙

  • 자신이 조작하는 객체의 내부 속사정은 몰라야 한다는 법칙
  • 객체는 자료를 숨기고 함수를 공개한다. 즉, 객체는 조회함수를 통해 내부 구조를 공개하면 안된다는 의미임
  • 즉 "클래스 C의 메소드f는 다음과 같은 객체의 메소드만 호출해야 한다고 주장"
    1. 클래스 C
    2. f가 생성한 객체
    3. f인수로 넘어온 객체
    4. C인스턴스 변수에 저장된 객체
  • 예제가 없어서 예제로 구현
public Class C{
  private int a;
  private double b;
  
  //가능.1
  public String f(AnotherClass parameter){
    double result = parameter.getData() + this.a + this.b;
    return Double.toString(result);
  }
  
  //불가능
  /*
  * f가 생성한 객체가 또 다른 객체를 생성하고 있기 때문에 디미터 법칙에 어긋남.
  */
  public String f(AnotherClass parameter){
    double result = parameter.getData().getNum() + this.a + this.b;
    return Double.toString(result);
  }
  
  //가능2
  public String f(AntotherClass parameter){
    double result = parameter.getDataNum() + this.a + this.b;
    return Double.toString(result);
  }
}

즉, 디미터 법칙을 만족시키기 위해선 AnotherClass에서 생성한 객체를 호출한 객체에서 사용할 수 있도록 클래스를 만들어줘야함

  • 좀더 이해하기 쉬운 예제로 변환
@Getter // 자동으로 getEmail(), getName(), getAddress()를 만들어주는 표현식이라 보면됨
public class User{
  private String email;
  private String name;
  private Address address;
}

public class Address {
  private String region;
  private String details;
}

public class SendService{
  
  //디미터 법칙 어긋남
  public void sendMessageToSeoulRegion(User user){
    if("서울".equals(user.getAddress().getRegion())){
      sendMessage();
    }
  }
  
  //디미터 법칙 만족
  public void sendMessageToSeoulRegion(User user){
    if(user.isSeoulRegion()){
      sendMessage();
    }
  }
}

// User에 method 추가 및 어떤 리전인지 확인하는 책임을 부여해줘서 디미터 법칙 만족
public class User{
  //생략
  public boolean isSeoulRegion(){
    return "서울".equals(this.address().getRegion());
  }
  
}

그리고 A.getB().getC().getD() 과 같이 계속 호출에 호출을 하는 것을 기차 충돌이라고 한다고 하니 기억해두면 될듯.

자료 전달 객체

자료 구조체의 전형적인 형태로 공개 변수만 있고 함수가 없는 클래스.

public class DTO {
  public int a;
  public double b;
  public String c;
}

이책에서 말하는 DTO는 VO, DTO, Entity를 하나로 합친듯한 느낌이 많이 들음.

책에서 말하는 DTO는 다음과 같음

  • 데이터 베이스에 저장된 가공되지 않은 정보를 어플리케이션 코드에서 사용할 객체로 변환하는 일련의 단계에서 가장 처음으로 사용하는 구조체

  • 공개 변수만 존재

그래서 내가 알고있는 VO, DTO, Entity에 대해서 먼저 설명을 해보겠음

종류설명
VO (Value Object)읽기 전용으로 값을 조작할 수 없음, 더이상 객체의 변경이 없는 최종적인 형태 일때 사용 (view로 보내줄때)
DTO(Data Transfer Object)비즈니스 코드간의 데이터를 전달할 때 사용하는 클래스
EntityDB와 직접적인 데이터를 주고 받을 때 사용하는 클래스

그림으로 표현하자면 이렇게 되겠네

예제는 서버단에 적합한 구조이지 않을까 생각이 들어서 생략

결론

  • 객체 지향적인 문제 풀이가 가장 효율적인 문제 풀이법이 아니다.
  • 절차 지향적인 부분을 사용하여 문제점을 해결하는 방법이 더 효율적이라면, 편견없이 최선의 해결책을 선택하도록 하자.

내가 내린 솔직한.. 결론..

  • 객체 지향적인 문제 풀이가 가장 효율적인 문제 풀이법이 아닐 수 있다.
  • 그렇지만 되도록 객체 지향으로 유연한 설계가 가능하도록 짜자..
  • 사실 절차지향, 객체지향이 정확히 무엇인지에 대해 정의만 알뿐이지 깊이 있게 알지 못한다라는 생각이 들었고 그래서 객체지향에 대한 깊이를 좀 더 공부해야 겠다라는 생각
  • 이런상태에서 무턱대고 객체 지향 + 절차지향이 된다면 나뿐만아니라 많은 사람들이 코드를 보게 될 것인데 다소 혼란을 주기가 쉽다 생각했다.
    • (자료구조가 디미터 법칙이 적용이 안된다는점 등등을 봤을때)
profile
수동적인 과신과 행운이 아닌, 능동적인 노력과 치열함

0개의 댓글