클래스를 쓰는 이유
Hermaeus Mora · · Backend
1. 캡슐화
가장 핵심적인 이유. 변수를 특정 메서드로만 제어하기 위함이다. 예를 들어 아무 함수나 balance 변수에 접근할 수 있다면 잔고가 음수가 되는 경우가 발생할 수 있다. 이때 withdraw 메서드만 balance 변수에 접근하도록 강제하는 것이 캡슐화이다.
public final class Account {
private long balance; // 외부 접근 불가
public void withdraw(long amount) {
if (amount <= 0) throw new IllegalArgumentException("amount must be positive");
if (amount > balance) throw new IllegalStateException("insufficient funds");
balance -= amount;
}
public long balance() { return balance; }
}2. 다형성
다형성은 호출자가 구현체를 몰라도 사용할 수 있도록 한다. 예를 들어 Java의 InputStream 추상 클래스의 구현체는 파일, HTTP 응답 바디, gzip 등으로 다양하다.
만약 countLines 함수를 아래와 같이 작성한다면
static int countLines(InputStream in) throws IOException {
byte[] buf = new byte[4096];
int lines = 0, k;
while ((k = in.read(buf)) != -1) {
for (int i = 0; i < k; i++) if (buf[i] == '\n') lines++;
}
return lines;
}해당 함수는 입력 파라미터의 구현체가 파일인지, HTTP 응답 바디인지, gzip인지 몰라도 사용할 수 있다.
// gzip 파일
...
try (InputStream file = new GZIPInputStream(new FileInputStream("logs.json.gz"))) {
System.out.println(countLines(file));
}
// HTTP 응답 바디
...
HttpResponse<InputStream> resp = HttpClient.newHttpClient().send(
HttpRequest.newBuilder(URI.create("https://api.github.com")).build(),
HttpResponse.BodyHandlers.ofInputStream());
try (InputStream body = resp.body()) {
System.out.println(countLines(body));
}
// 소켓
...
try (InputStream sock = socket.getInputStream()) {
System.out.println(countLines(sock));
}다른 예로, 두 클래스를 합성하는 경우가 있다.
interface Engine { void idling(); }
interface Transmission { void buildPressure(); }
class Car {
private final Engine engine;
private final Transmission transmission;
public Car(Engine e, Transmission t) {
this.engine = e;
this.transmission = t;
}
public void start() {
this.engine.idling();
this.transmission.buildPressure();
}
}
...
new Car(new GasolineEngine(), new ManualTransmission()).start();
new Car(new ElectricEngine(), new AutoTransmission()).start();이 경우에도 Car는 Engine과 Transmission이 어떻게 구현되었는지 몰라도 사용할 수 있다.
3. 상속
위의 countLines 메서드는 스트림을 소비하기 때문에 다른 작업을 추가하려면 다시 스트림을 읽어야 한다. 만약 읽는 동시에 카운트를 한다면 더 효율적일 것이다. 이때 상속을 사용하여 InputStream 의 모든 기능을 그대로 쓰면서 기능을 추가할 수 있다.
class LineCountingInputStream extends FilterInputStream {
private long lines = 0;
public LineCountingInputStream(InputStream in) { super(in); }
@Override
public int read() throws IOException {
int b = in.read();
if (b == '\n') lines++;
return b;
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
int n = in.read(b, off, len);
if (n > 0) {
for (int i = off; i < off + n; i++) {
if (b[i] == '\n') lines++;
}
}
return n;
}
public long lines() { return lines; }
}
try (LineCountingInputStream in = new LineCountingInputStream(new GZIPInputStream(new FileInputStream("logs.json.gz")))) {
byte[] data = in.readAllBytes();
doSomething(data);
System.out.println(in.lines());
}