객체지향 프로그래밍(Object-Oriented Programming, OOP)은 프로그래밍 패러다임 중 하나로, 데이터와 해당 데이터를 조작하는 기능을 하나의 객체(object)로 묶어서 프로그래밍하는 방식입니다.
객체는 특정한 기능을 수행하는 코드와 그 코드에서 사용되는 데이터를 함께 묶어서 하나의 단위로 취급합니다. 이 방식을 통해 코드의 재사용성과 유지보수성을 높일 수 있습니다.
예를 들어, 은행 계좌 객체에서는 계좌의 잔액 정보를 private으로 선언하고, 입금/출금 메서드를 public으로 선언하여 외부에서는 계좌의 잔액 정보에 직접적인 접근이 불가능하도록 합니다.
private static class Bank{
private int balacne;
public int deposit(int deposit){
this.balacne += deposit;
return balacne;
}
public int withdraw(int withdraw){
if (balacne - withdraw < 0){
throw new IllegalArgumentException("잔액 부족");
}
this.balacne -= withdraw;
return balacne;
}
}
예를 들어, 동물 클래스에서 상속받은 개 클래스와 고양이 클래스는 모두 동물 클래스가 가지고 있는 속성과 메서드를 사용할 수 있습니다.
public class Person {
String name;
private int age;
public void speak() {
System.out.println("저는 " + name + "입니다.");
}
}
class Dancer extends Person {
public void dance() {
System.out.println(name + ": 춤을 춥니다.");
}
public static void main(String[] args) {
Dancer dancer = new Dancer();
dancer.speak(); // 부모 객체의 매서드 사용 가능
dancer.dance();
}
}
예를 들어, 동물 클래스에서 makeSound() 메서드를 정의하고, 이를 상속받은 개 클래스와 고양이 클래스에서 각각 오버라이딩하여 자신만의 makeSound() 메서드를 정의할 수 있습니다.
class Animal {
public void makeSound() {
System.out.println("The animal makes a sound");
}
}
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("The dog barks");
}
}
class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("The cat meows");
}
}
public class Main {
public static void main(String[] args) {
Animal myAnimal = new Animal();
Animal myDog = new Dog();
Animal myCat = new Cat();
myAnimal.makeSound();
myDog.makeSound();
myCat.makeSound();
}
}
예를 들어, 사람 클래스에서 주민등록번호, 이름, 나이 등의 속성을 추상화하여 Person 클래스의 속성으로 정의할 수 있습니다.
public abstract class Person {
private String residentRegistrationNumber;
private String name;
private int age;
public Person(String residentRegistrationNumber, String name, int age) {
this.residentRegistrationNumber = residentRegistrationNumber;
this.name = name;
this.age = age;
}
public String getResidentRegistrationNumber() {
return residentRegistrationNumber;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public void setResidentRegistrationNumber(String residentRegistrationNumber) {
this.residentRegistrationNumber = residentRegistrationNumber;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public abstract void work();
}
상속받는 자식 클래스도 정의해봅니다.
public class Student extends Person {
private String studentNumber;
private String major;
public Student(String residentRegistrationNumber, String name, int age,
String studentNumber, String major) {
super(residentRegistrationNumber, name, age);
this.studentNumber = studentNumber;
this.major = major;
}
public String getStudentNumber() {
return studentNumber;
}
public String getMajor() {
return major;
}
public void setStudentNumber(String studentNumber) {
this.studentNumber = studentNumber;
}
public void setMajor(String major) {
this.major = major;
}
@Override
public void work() {
System.out.println("Study in the " + major + " department");
}
}
객체지향 프로그래밍에서는 데이터와 그 데이터를 조작하는 기능을 하나의 클래스(class)로 정의합니다. 클래스는 객체를 생성하는데 사용되는 템플릿으로, 객체의 공통된 속성과 기능을 정의합니다. 쉽게 접할 수 있는 붕어빵 예시에서의 "붕어빵 틀" 이라고 볼 수 있습니다.
이러한 클래스를 이용해 객체를 생성하면, 해당 객체는 클래스에서 정의한 속성과 기능을 상속받아 사용할 수 있습니다.
이를테면, 각각의 자동차 객체는 모두 브랜드, 모델, 색상 등의 속성을 가지며, 주행, 정지, 가속 등의 기능을 공통으로 수행할 수 있습니다.
SOLID는 객체지향 프로그래밍에서 설계 원칙을 나타낸 것으로, 다음의 다섯 가지 원칙으로 이루어져 있습니다.
SRP(Single Responsibility Principle, 단일 책임 원칙):
클래스는 하나의 책임만을 가져야 하며, 그 책임을 완전히 캡슐화해야 합니다.
OCP(Open-Closed Principle, 개방-폐쇄 원칙):
클래스는 확장에는 열려 있으나 변경에는 닫혀 있어야 합니다. 다시 말해, 새로운 기능이 추가될 때는 기존의 코드를 변경하지 않고 확장이 가능해야 합니다.
LSP(Liskov Substitution Principle, 리스코프 치환 원칙):
자식 클래스는 부모 클래스에서 가능한 모든 동작을 수행할 수 있어야 합니다. 즉, 자식 클래스는 부모 클래스의 인터페이스를 준수해야 합니다.
ISP(Interface Segregation Principle, 인터페이스 분리 원칙):
인터페이스는 클라이언트가 필요로 하는 메서드만 포함해야 합니다. 클라이언트가 필요로 하지 않는 메서드는 포함하지 말아야 합니다.
DIP(Dependency Inversion Principle, 의존 역전 원칙):
고수준 모듈은 저수준 모듈에 의존하면 안 됩니다. 대신 추상화된 인터페이스에 의존해야 합니다.