2016/03/06 - [java] - 추상화(Abstraction) 이해의 중요성 #2



추상화 이해의 중요성에 대한 3번째 글입니다. 2번째 글에선 자동차(Car)의 속성에 해당하는 Car.drivingDistance, Car.dateMadeAt, Car.Color, Car.Engine 등을 getter-setter method로 왜 Encapsulation(캡슐화)을 하는지 살펴보았습니다.


엔진(Engine)이라는 것도 생각해 보니 제조사가 어디인지 몇 마력인지 모델은 또 무엇인지 등 복잡한 정보를 가지고 있는 객체란 생각이 듭니다. Engine에 대한 추상화도 필요해 보입니다. 휠(Wheel), 제조사, 트렁크, 루프도 추상화가 필요해 보입니다. 쉽게 아래 테이블처럼 자동차 각 구성요소를  클래스로 추상화 할수 있습니다.


자동차 구성요소

추상(Abstraction) 

 엔진(Engine)

Engine 

 휠(Wheel)

Wheel

 제조사(Manufacturer)

Manufacturer

 트렁크(Trunk)

Trunk 

 루프(Roof)

Roof 


여기서 주의해야 할 사항이 있습니다. 추상화를 너무 과도하게 하지 않는가 생각해 보아야 합니다. primitive type, String으로 충분히 요구사항을 만족시킬 수 있고 추후 요구사항 및 변화에도 유연할 수 있다면  클래스화 하지 않을 수도 있습니다. Oracle HotSpot 32bit JVM 기준으로 한 객체는 8 바이트의 헤더를 가지고 있고 배열(Array)은  12 바이트의 헤더를 가지고 있습니다. 즉, 하나의 객체는 최소 8byte 메모리를 소모하고 하나의 배열은 최소 12byte 메모리를 소모 합니다. 메모리를 고려해야할 시스템 환경이라면 클래스 대신 byte, boolean, char, short, int, float, long, double과 같은 primitive type과 String으로 쓰는 것을 고려할 수있습니다. 원래 Car 클래스가 String 멤버 변수로 이루어진 것처럼 말이지요.


이 글에서는 윗 테이블처럼 추상화를 했다고 가정합니다. Car.color는 int로 변경하고 Car.dateMadeAt은 java.util.Date 또는 java 8의 java.time.LocalDateTime을 사용할 수 있습니다. 그러나 Car.dateMadeAt을 memory efficient하게 사용하기 위해 String으로 사용하고 java.util.DateTime 이나 java.time.LocalDateTime으로 변환하는 메소드를 제공하도록 하겠습니다.

package com.jayjaylab.app.usedcar

public class Car {
    int drivingDistance;    // in km(killo meters)
    String dateMadeAt;    // in 2007-12-03T10:15:30 format
    int color;
    Engine engine;
    Wheel wheel;
    Manufacturer manufacturer;
    String model;
    Trunk trunk;
    Roof roof;
    // and so on...

    public void setEngine(Engine engine) {
        this.engine = engine;
    }

    public void setWheel(Wheel wheel) {
        this.wheel = wheel;
    }

    public void setRoof(Roof Roof) {
        this.roof = roof;
    }
    
    public void setDrivingDistance(int drivingDistance) {
        this.drivingDistance = drivingDistance;
    }

    public java.util.Date getDateMadeAt() {
        return new java.util.Date(dateMadeAt);
    }
    
    public java.time.LocalDateTime getLocalDateTimeMadeAt() {
        return java.time.LocalDateTime.parse(dateMadeAt);
    }

    ....

    public static void main(String[] args) {
        Car car = new Car();
        // 엔진 변경
        car.setEngine(new Engine("yamato"));
        car.setEngine(new Engine("superengine"));
        // 휠 변경
        car.setWheel(new Wheel("kumho_wheel"));
        car.setWheel(new Wheel("fantastic_wheel"));
        // 루프 변경
        car.setRoof(new Roof("default_roof"));
        car.setRoof(new Roof("sun_roof"));
        // 주행 거리 변경. 불법 이죠잉!
        car.setDrivingDistance(100);  // 100 km
        car.setDrivingDistance(200); // 200 km
    }
} 

자동차 중고매매 사업장에서의 쓰일 자동차 Abstraction이긴 한데, 개발사 회사의 입장으로선 굳이 자동차 매매만 되는 애플리케이션으로 한정시킬 필요가 없을것 같습니다. 추후에 자동차 뿐만 아니라 오토바이, 자전거등의 사업장에서도 사용될 수 있는 확장성을 좀 더 고려하면 개발한 제품을 공급할 곳이 더 많아지겠지요. 그래서 저는 Vehicle이란 인터페이스를 만들어서 Car, Bike가 상속하게 해야할지 Buyable이란 인터페이스를 만들어서 Car, Bike가 상속하게 해야할지 고민이네요. Vehicle이란 인터페이스가 가질 Method는 go(), stop() 등의 자동차가 하는 행위일 것 같은데 사실 매매용 소프트웨어이기 때문에 자동차가 하는 행위 자체를 선언하는 것은 별 쓸모가 없어보이고 매매 시스템이니깐 매매되는 자동차라는 점에 중점을 두면 Buyable이란 인터페이스를 만들어 isSold(), buy()등의 매매 관련 메소드를 정의하는게 좀더 좋은 선택으로 보입니다.

그럼 한번 Buyable이란 인터페이스를 정의해 보겠습니다. 알아둬야 할 사항으로  자바에서는 대개 OOP의 Polymorphism(다형성)을 지원하기 위해 Interface을 정의하고 이를 상속하는 기법을 사용합니다. 특정 인터페이스를 상속하는 몇몇 클래스들이 인터페이스에 선언된 메소드의 정의가 동일하다면 그 인터페이스를 상속하는 Abstract 클래스를 정의하고 이 Abstract 클래스를 다시 상속하는 기법이 널리 쓰입니다. 그런데 Java 8에서는 interface에 default method와 static method란게 도입되어서 이젠 그런 기법이 서버쪽에서는 안쓰일 것 같네요. 서버 개발에 대한 실무 경험이 없어 장담하긴 힘듭니다.;; 안드로이드는 아직 Java 7만 지원이 되어서 java 8은 사실 고려 안해도 되구요.

[Java 8 Interface의 default methods, static methods 관련 글은 여기로]

package com.jayjaylab.app.usedcar

/**
 * 매매할 수 있는 객체를 나타냅니다.
 */
public interface Buyable {
    /**
     * 해당 상품이 다 팔렸는지 알기 위해 호출합니다.
     * @return true 이미 다 팔림, false 재고가 남아 있음.
     */
    boolean isSoldOut();

    /**
     * 구매할 때 호출합니다.
     * @param option 구매할 때 추가할 옵션들입니다.
     * @return true 구매 신청이 정상적으로 완료됨, false 구매 실패
     */
    boolean buy(Option option);

    ...
}
package com.jayjaylab.app.usedcar

public class Car implements Buyable {
    int drivingDistance;    // in km(killo meters)
    String dateMadeAt;    // in 2007-12-03T10:15:30 format
    int color;
    Engine engine;
    Wheel wheel;
    Manufacturer manufacturer;
    String model;
    Trunk trunk;
    Roof roof;
    // and so on...

    public boolean isSoldOut() {
        return false;
    }

    public boolean buy(Option option) {
        return true;
    }
    
    ...
}


음, 위에 것처럼 구현하니 왠지 매매할 수 있을것 같은 자동차 Abtraction이 만들어진 것 같네요. 이번 글은 우선 여기까지하고 시간될 때 다시 돌아오도록 합죠.

Posted by 제이제이랩
,

2016/03/02 - [java] - 추상화(Abstraction) 이해의 중요성 #1


자동차는 영어로 Car이고, 자동차를 이루는 구성요소로는 엔진(Engine), 바퀴(Wheel), 제조사(Manufacturer), 모델(Model) 등이 있습니다. 중고매매에서 가장 중요한 주행거리, 연식, 자동차 색상도 놓칠순 없겠군요. 단순한 자동차(Car) 추상화는 java.lang.Object만을 상속하는 Car 클래스를 만드는 것입니다. 다른 말로 POJO(Plain Old Java Object)를 만드는 것입니다.

자동차 추상(Abstraction)은 com.jayjaylab.app.usedcar.Car 클래스 아래와 같이 만들었습니다.

package com.jayjaylab.app.usedcar

public class Car {
    public int drivingDistance;    // in km(killo meters)
    public String dateMadeAt;
    public String color;
    public String engine;
    public String wheel;
    public String manufacturer;
    public String model;
    public String trunk;
    public String roof;
    // and so on...
} 

자동차는 제조사에서 한번 만들어지면 제조사, 모델이 변경되지 않습니다. 위처럼 자동차를 단순하게 추상화했는데, 자동차에서 자주 변경이 되는 부분, 변할 수 있는 부분이 무엇이 있을까 생각해 봅시다.

  • 엔진이 바뀔 수 있다.
  • 휠이 바뀔 수 있다.
  • 주행 거리가 바뀔 수 있다. 자동차 중고매매 사업장에서 이런 요구사항이 있다면 주행 거리 조작! 불법이지요;;
  • 루프가 바뀔 수 있다.

상기 정도로 정리해 볼 수 있습니다. 위 Car 클래스가 이런 변경 사항에 충분히 유연한지 한번 점검해 보겠습니다.

package com.jayjaylab.app.usedcar

public class Car {
    public int drivingDistance;    // in km(killo meters)
    public String dateMadeAt;
    public String color;
    public String engine;
    public String wheel;
    public String manufacturer;
    public String model;
    public String trunk;
    public String roof;
    // and so on...

    public static void main(String[] args) {
        Car car = new Car();
        // 엔진 변경
        car.engine = "yamato";
        car.engine = "superengine";
        // 휠 변경
        car.wheel = "kumho_wheel";
        car.wheel = "fantastic_wheel";
        // 루프 변경
        car.roof = "default_roof";
        car.roof = "sun_roof";
        // 주행 거리 변경. 불법 이죠잉!
        car.drivingDistance = 100;  // 100 km
        car.drivingDistance = 200; // 200 km
    }
} 

위 소스 코드를 보니, 런타임 동안 자동차 엔진, 휠, 루프, 주행거리 변경을 손 쉽게 할 수 있습니다. 그런데 엔진, 휠, 루프, 주행거리를 변경하는데 Car 클래스의 멤버변수를 직접 접근해서 변경해야 합니다. Car 클래스를 사용하는 입장에서 Car 클래스의 멤버 변수가 무엇인지 알 필요가 없습니다. 엔진을 변경하기 위해 Car 클래스에서는 Car.engine이라는 멤버 변수를 외부에 노출시키는게 아닌 엔진을 변경할 수 있는 메소드를 제공해야할 것입니다. Car 클래스 사용자들(개발자들)에 Car 클래스의 속성을 변경 시키려면 메소드를 통하여 변경 시키도록 하고 해당 멤버 변수는 개발자에게 노출시키지 않도록 합니다. 위의 Car 클래스는 캡슐화(Encapsulation)이 잘못된 예의 하나입니다. 아래와 같이 Accessor를 public에서 protected로 변경하도록 하고 멤버 변수에 대한 직접 접근을 막습니다. 사실 멤버 변수를 직접 참조하는것이 메소드를 호출하는 것보다 몇 CPU 싸이클 빠를것 같지만 난독화 때문에 Proguard 사용하면 신경 안써도 됩니다. getter-setter inlining을 proguard가 해준다고 하네요. [참고 http://developer.android.com/training/articles/perf-tips.html#GettersSetters]

package com.jayjaylab.app.usedcar

public class Car {
    int drivingDistance;    // in km(killo meters)
    String dateMadeAt;
    String color;
    String engine;
    String wheel;
    String manufacturer;
    String model;
    String trunk;
    String roof;
    // and so on...

    public void setEngine(String engine) {
        this.engine = engine;
    }

    public void setWheel(String wheel) {
        this.wheel = wheel;
    }

    public void setRoof(String Roof) {
        this.roof = roof;
    }
    
    public void setDrivingDistance(int drivingDistance) {
        this.drivingDistance = drivingDistance;
    }

    ...

    public static void main(String[] args) {
        Car car = new Car();
        // 엔진 변경
        car.setEngine("yamato");
        car.setEngine("superengine");
        // 휠 변경
        car.setWheel("kumho_wheel");
        car.setWheel("fantastic_wheel");
        // 루프 변경
        car.setRoof("default_roof");
        car.setRoof("sun_roof");
        // 주행 거리 변경. 불법 이죠잉!
        car.setDrivingDistance(100);  // 100 km
        car.setDrivingDistance(200); // 200 km
    }
} 

Car 클래스 사용자가 엔진, 휠 등을 변경하기 위해서 위 처럼 set method를 사용하도록 강제하면 Car의 멤버 변수를 잘못된 값으로 변경하는 것을 막을 수 있습니다. 예를 들면, Car.setEngine(), Car.setWheel()의 메소드에 매개변수가 유효한지 체크하는 로직을 넣거나 현재 변경 가능한 상태에만 변경을 하도록 로직을 추가하여 각 변경을 안전하게 수행하도록 합니다. 또한 메소드를 통하여 변경을 할 경우 멀티 쓰레딩 환경에서 thread safety 하도록 로직을 추가할 수도 있습니다. 응용 프로그램을 개발할 때 절대 네버 멤버 변수를 public로 선언하여 직접 접근하는 방식을 피하셔야 합니다. getter-setter로 Encapsulation 하시는 것은 기본입니다. 예외인 경우는 자료 구조 만들 때인데요. 성능(메소드 사용으로 인한 스택 접근 시간)을 위해 cpu cycle 사용을 최소화하기 위한 노력을 자료 구조에서는 해줘야하는데, 자료 구조 내부에서 getter-setter를 이용하지 않고 멤버 변수에 직접 접근을 하는 경우가 있습니다. 안드로이드의 경우는 ViewHolder 패턴을 사용할 때 멤버 변수를 직접 접근합니다. ViewHolder 클래스 내부 멤버변수를 접근할 때 getter로 접근하지 않고 멤버 변수를 직접 접근해서 UI thread 사용 시간을 최소화 해줘야 합니다.


잡설이 많았는데, 그럼 다음 글에서 더욱 제대로된 Car abstraction을 만들어 봅시다.

Posted by 제이제이랩
,

최근에 Jake Warton의 Retrofit 라이브러리 발표 영상을 보고 추상화에 대해서 곰곰히 생각해 보았습니다. 아래 동영상인데요. Jake Warton의 발표를 보면 header abstraction, response abstraction, Call encapsulation 등의 용어를 사용하여 Retrofit을 설명합니다. 이 부분이 저에게는 좀 충격으로 다가왔는데요. 내가 OOP 개념에 대해 대학교 때 공부한 이후에 고민해 본적이 있는가? 나는 코드 설계할때 뭘 고려하지? 나는 저런 용어를 사용하여 내 코드를 설명할 수 있는가? 등등의 생각이 들었드랬죠.




애플리케이션 개발자는 기획에서 내려온 요구사항은 기본적으로 만족시켜야 하고 차후 변경에도 유연하고 확장성 있는 코드를 설계하고 구현 해야합니다. 이 글에서는 유지 보수 용이하고 확장성 있는 코드를 설계하고 구현하기 위해선 뭐가 중요할까 고민해 보는데 그 핵심은 추상화로 보고 고민해 볼까 합니다.


추상화라는 것을 간단하게 말하면 복잡한 것을 간추려서 간단하게 하는 것인데요. 사실 추상화라는 개념은 인간의 사고 방식과 빼놓을 수 없을 것 같습니다. 어떤  유무형의 사물을 볼 때 내 기준에 비추어서 흥미있고 필요한 부분만을 보고 그것에 대해 머리속으로 간단히 기억합니다. 제 생각엔 이미 인간의 인식, 사고, 기억하는 단계에 이미 추상화를 하고 있는것으로 보입니다. 대부분의 사물의 복잡하고 자세한 내부는 외부에 감추어져 있고 사람이란 동물은 꼭 필요하지 않으면 시간 써가면 자세한 내부사항은 알 필요가 없죠. 


만약 자신의 고용주가 어떤 개발 프로젝트를 따와서 본인에게 던져줬다고 합시다. 음, 구체적인게 좋겠군요. 자동차 중고매매 사업장에서 각 딜러가 어떤 차를 누구에게 팔았고, 어떤 차가 재고로 있으며 어떤 차가 새로 들어왔는지등을 확인할 수 있는 애플리케이션 개발을 해야한다고 합시다. 그럼 이 애플리케이션 개발하기 앞서 설계를 하고 개발을 해야하겠는데요. 시스템의 전체적인 설계는 소프트웨어 아키텍쳐 패턴에서 적절한 패턴을 이미 선택했다고 가정하고 이 시스템을 구성하는 객체가 무엇이 있는지 각 관계는 무엇인지 파악하는 단계로 들어섰다고 합시다.


그래서 자동차(Car)를 설계해야 하는데, 요구사항이 무엇인지에 따라 차후 어떤 변경이 있을지에 따라 자동차의 추상화 수준을 결정해야 합니다. 자동차 추상화(Car abstraction)작업을 그럼 해볼까요?



Posted by 제이제이랩
,