객체지향 프로그래밍(Object-Oriented Programming, OOP)은 객체들의 집합을 통해 프로그램의 상호작용을 표현하고, 데이터를 객체로 취급하여 해당 객체 내부에 선언된 메서드를 활용하는 프로그래밍 방식이다. 객체지향 설계는 시간이 많이 소요될 수 있으며, 실행 속도는 다른 프로그래밍 패러다임에 비해 느릴 수 있다.
자연수로 이루어진 배열에서 최댓값을 찾으라고 한다면 다음과같이 로직을 구성한다.
const ret = [1, 2, 3, 4, 5, 11, 12]
class List {
constructor(list) {
this.list = list
this.mx = list.reduce((max, num) => num > max ? num : max, 0)
}
getMax() {
return this.mx
}
}
const a = new List(ret)
console.log(a.getMax()) // 12
List클래스는 배열을 받아 최댓값을 구하는 로직을 캡슐화한다.
이를 통해 객체를 통해 필요한 작업을 수행할 수 있다.
class Animal {
public void sound() {
System.out.println("Some sound");
}
}
class Dog extends Animal {
@Override
public void sound() {
System.out.println("Bark");
}
}오버로딩(overloading)은 같은 이름을 가진 메서드를 여러 개 두는 것을 말한다. 메서드의 타입, 매개변수의 유형, 개수 등으로 여러 개를 둘 수 있으며 컴파일 중에 발생하는 '정적' 다형성이다.
class Person {
public void eat(String a) {
System.out.println("I eat " + a);
}
public void eat(String a, String b) {
System.out.println("I eat " + a + " and " + b);
}
}
public class CalculateArea {
public static void main(String[] args) {
Person a = new Person();
a.eat("apple");
a.eat("tomato", "phodo");
}
}
/*
I eat apple
I eat tomato and phodo
*/
매개 변수의 개수에 따라 다른 함수가 호출되는 것을 알 수 있다.
오버라이딩(overriding)은 주로 메서드 오버라이딩을 말하며 상위 클래스로부터 상속받은 메서드를 하위 클래스가 재정의하는 것을 의미한다.
이는 런타임중에 발생하는 '동적' 다형성이다.
class Animal {
public void bark() {
System.out.println("mumu! mumu!");
}
}
class Dog extends Animal {
@Override
public void bark() {
System.out.println("wal!!! wal!!!");
}
}
public class Main {
public static void main(String[] args) {
Dog d = new Dog();
d.bark();
}
}
/*
wal!!! wal!!!
*/
자식 클래스 기반으로 메서드가 재정의됨을 알 수 있다.
객체지향 설계에서 SOLID 원칙은 코드의 유지보수성과 확장성을 보장하기위해 반드시 지켜야할 기준이다.
단일 책임 원칙(SRP, Single Responsibility Principle)
클래스는 단 하나의 책임만 가져야 한다.
개방-폐쇄 원칙(OCP, Open Closed Principle)
코드는 확장에는 열려 있어야 하고, 수정에는 닫혀 있어야 한다. 즉 기존 코드는 잘 변경하지 않으면서 확장은 쉽게 할 수 있어야 한다.
리스코프 치환 원칙(LSP, Liskov Substitution Principle)
하위 타입은 상위타입으로 대체 가능해야 한다.
인터페이스 분리 원칙(ISP, Interface Segregation Principle)
하나의 일반 인터페이스보다 구체적인 여러 인터페이스로 나누어야 한다.
의존 역전 원칙(DIP, Dependency Inversion Principle)
상위 계층은 하위 계층에 의존하지 않고, 추상화된 인터페이스에 의존해야 한다.
예를 들어 타이어를 교체할 수 있는 인터페이스를 설계하면 다양한 종류의 타이어로 교체가 가능하다.
interface Tire {
void rotate();
}
class Car {
private Tire tire;
public Car(Tire tire) {
this.tire = tire;
}
public void drive() {
tire.rotate();
}
}