객체 지향 프로그래밍
객체의 관점에서 프로그래밍을 하는 것.
프로그래밍에서 필요한 데이터를 추상화시켜 객체를 만들고, 객체들 간의 상호작용을 통해 로직을 구성하는 프로그래밍 방법론
Java, C++, Python ...

1. 강일구라는 사람은 머리 2개, 다리 3개, 팔 4개를 가지고 있고, 경찰이다.
2. 강일구.소개함수()
3. 여진구라는 사람은 머리 2개, 다리 3개, 팔 4개를 가지고 있고, 의사이다.
4. 여진구.소개함수()
1. class 정의 (인간 데이터, 함수, 추가 변수)
2. 강일구 = class 객체(경찰)
3. 여진구 = class 객체(의사)
5. 강일구.소개함수()
6. 여진구.소개함수()
// 커피를 만드는 책임, 커피 머신을 청소하는 책임, 사용자에게 알림을 보내는 책임을 모두 가지고 있음.
// 만약 커피 만드는 방법이 변경되거나 청소 방법이 변경된다면, CoffeeMachine 클래스는 여러 이유로 변경될 수 있음.
class CoffeeMachine {
public void brewCoffee() {
// 커피를 만드는 로직
System.out.println("Brewing coffee...");
}
public void cleanMachine() {
// 커피 머신을 청소하는 로직
System.out.println("Cleaning the machine...");
}
public void notifyUser() {
// 사용자에게 알림을 보내는 로직
System.out.println("Your coffee is ready!");
}
}
// 커피를 만드는 클래스
// 책임 : 커피를 만드는 모든 작업
// 변경의 이유 : 커피를 만드는 방법이나 종류, 커피의 양이나 농도를 조절하는 방법이 변경될 때
class CoffeeMachine {
public void brewEspresso() {
System.out.println("Brewing espresso...");
}
public void brewLatte() {
System.out.println("Brewing latte...");
}
public void setCoffeeStrength(int strength) {
System.out.println("Setting coffee strength to " + strength);
}
public void setCoffeeAmount(int amount) {
System.out.println("Setting coffee amount to " + amount + "ml");
}
}
// 청소를 담당하는 클래스
// 책임 : 커피 머신의 청소 작업
// 변경의 이유 : 청소 방법이나 청소 과정이 변경될 때
class CleaningService {
public void startCleaning() {
System.out.println("Starting cleaning process...");
}
public void cleanWaterTank() {
System.out.println("Cleaning water tank...");
}
public void cleanCoffeeGroundsContainer() {
System.out.println("Cleaning coffee grounds container...");
}
public void notifyCleaningRequired() {
System.out.println("Cleaning required soon.");
}
}
// 알림을 담당하는 클래스
// 책임 : 사용자에게 알림을 보내는 작업
// 변경의 이유 : 알림의 내용이나 알림 방법이 변경될 때
class UserNotifier {
public void notifyCoffeeReady() {
System.out.println("Your coffee is ready!");
}
public void notifyMaintenanceRequired() {
System.out.println("Maintenance required.");
}
public void notifyLowWaterLevel() {
System.out.println("Water level is low.");
}
}
확장에는 열려있고, 수정에는 닫혀있는. 기존의 코드를 변경하지 않으면서(Closed), 기능을 추가할 수 있도록(Open) 설계가 되어야 한다는 원칙.
기능 추가 요청이 오면 확장을 통해 손쉽게 구현하면서, 확장에 따른 클래스 수정은 최소화 하도록.
확장에 열려있다.
변경에 닫혀있다.
OCP를 위반한 예시
class Animal {
String type;
Animal(String type) {
this.type = type;
}
}
// 동물 타입을 받아 각 동물에 맞춰 울음소리를 내게 하는 클래스 모듈
class HelloAnimal {
void hello(Animal animal) {
if(animal.type.equals("Cat")) {
System.out.println("냐옹");
} else if(animal.type.equals("Dog")) {
System.out.println("멍멍");
}
}
}
public class Main {
public static void main(String[] args) {
HelloAnimal hello = new HelloAnimal();
Animal cat = new Animal("Cat");
Animal dog = new Animal("Dog");
hello.hello(cat); // 냐옹
hello.hello(dog); // 멍멍
}
}
// 새로운 동물을 추가하려면 ?
class HelloAnimal {
// 기능을 확장하기 위해서는 클래스 내부 구성을 일일히 수정해야 하는 번거로움이 생긴다.
void hello(Animal animal) {
if (animal.type.equals("Cat")) {
System.out.println("냐옹");
} else if (animal.type.equals("Dog")) {
System.out.println("멍멍");
} else if (animal.type.equals("Sheep")) {
System.out.println("메에에");
} else if (animal.type.equals("Lion")) {
System.out.println("어흥");
}
// ...
}
}
public class Main {
public static void main(String[] args) {
HelloAnimal hello = new HelloAnimal();
Animal cat = new Animal("Cat");
Animal dog = new Animal("Dog");
Animal sheep = new Animal("Sheep");
Animal lion = new Animal("Lion");
hello.hello(cat); // 냐옹
hello.hello(dog); // 멍멍
hello.hello(sheep);
hello.hello(lion);
}
}
// 추상화
abstract class Animal {
abstract void speak();
}
class Cat extends Animal { // 상속
void speak() {
System.out.println("냐옹");
}
}
class Dog extends Animal { // 상속
void speak() {
System.out.println("멍멍");
}
}
class HelloAnimal {
void hello(Animal animal) {
animal.speak();
}
}
public class Main {
public static void main(String[] args) {
HelloAnimal hello = new HelloAnimal();
Animal cat = new Cat();
Animal dog = new Dog();
hello.hello(cat); // 냐옹
hello.hello(dog); // 멍멍
}
}
// 위와 같이 설계한다면
// 추상클래스를 상속만 하면 메소드 강제 구현 규칙으로 규격화만 하면 확장에 제한 없다 (opened)
class Sheep extends Animal {
void speak() {
System.out.println("매에에");
}
}
class Lion extends Animal {
void speak() {
System.out.println("어흥");
}
}
// 기능 확장으로 인한 클래스가 추가되어도, 더이상 수정할 필요가 없어진다 (closed)
class HelloAnimal {
void hello(Animal animal) {
animal.speak();
}
}
public class Main {
public static void main(String[] args) {
HelloAnimal hello = new HelloAnimal();
Animal cat = new Cat();
Animal dog = new Dog();
Animal sheep = new Sheep();
Animal lion = new Lion();
hello.hello(cat); // 냐옹
hello.hello(dog); // 멍멍
hello.hello(sheep); // 매에에
hello.hello(lion); // 어흥
}
}

class Bird {
public void fly() {
System.out.println("Flying");
}
}
class Ostrich extends Bird {
@Override
public void fly() {
throw new UnsupportedOperationException("Ostriches cannot fly");
}
}
// 타조는 날 수 없기 때문에 Bird를 상속받았을 때 fly 메서드를 오버라이드 하여 예외를 발생시킴.
public void letBirdFly(Bird bird) {
bird.fly();
}
Bird sparrow = new Bird();
letBirdFly(sparrow); // 정상 작동
Bird ostrich = new Ostrich();
letBirdFly(ostrich); // 예외 발생: UnsupportedOperationException
// Bird 클래스로 대체되지 않아 LSP 를 위반함
// Bird 를 추상클래스로 만들고
abstract class Bird {
public abstract void makeSound();
}
// Flyable 인터페이스를 사용하여 날 수 있는 새들과 날 수 없는 새들 분리
interface Flyable {
void fly();
}
class Sparrow extends Bird implements Flyable {
@Override
public void makeSound() {
System.out.println("Chirp chirp");
}
@Override
public void fly() {
System.out.println("Flying");
}
}
class Ostrich extends Bird {
@Override
public void makeSound() {
System.out.println("Boom boom");
}
}
public class Main {
public static void letBirdFly(Flyable bird) {
bird.fly();
}
public static void main(String[] args) {
Sparrow sparrow = new Sparrow();
letBirdFly(sparrow); // 정상 작동: "Flying"
Ostrich ostrich = new Ostrich();
// letBirdFly(ostrich); // 컴파일 에러: Ostrich는 Flyable이 아님
Bird bird1 = new Sparrow();
bird1.makeSound(); // "Chirp chirp"
Bird bird2 = new Ostrich();
bird2.makeSound(); // "Boom boom"
}
}

// 스마트폰 인터페이스
interface ISmartPhone {
void call(String number); // 통화 기능
void message(String number, String text); // 문제 메세지 전송 기능
void wirelessCharge(); // 무선 충전 기능
void AR(); // 증강 현실(AR) 기능
void biometrics(); // 생체 인식 기능
}
// 최신 폰들은 가능
class S20 implements ISmartPhone {
public void call(String number) {
}
public void message(String number, String text) {
}
public void wirelessCharge() {
}
public void AR() {
}
public void biometrics() {
}
}
class S21 implements ISmartPhone {
public void call(String number) {
}
public void message(String number, String text) {
}
public void wirelessCharge() {
}
public void AR() {
}
public void biometrics() {
}
}
// 하지만 구식은 안됨. 쓸데 없는 메서드를 구현해야 함.
class S3 implements ISmartPhone {
public void call(String number) {
}
public void message(String number, String text) {
}
public void wirelessCharge() {
System.out.println("지원 하지 않는 기능 입니다.");
}
public void AR() {
System.out.println("지원 하지 않는 기능 입니다.");
}
public void biometrics() {
System.out.println("지원 하지 않는 기능 입니다.");
}
}
// 기능별로 인터페이스 분리
interface IPhone {
void call(String number); // 통화 기능
void message(String number, String text); // 문제 메세지 전송 기능
}
interface WirelessChargable {
void wirelessCharge(); // 무선 충전 기능
}
interface ARable {
void AR(); // 증강 현실(AR) 기능
}
interface Biometricsable {
void biometrics(); // 생체 인식 기능
}
// 각각 필요한 기능만 implements 하면 됨
class S21 implements IPhone, WirelessChargable, ARable, Biometricsable {
public void call(String number) {
}
public void message(String number, String text) {
}
public void wirelessCharge() {
}
public void AR() {
}
public void biometrics() {
}
}
class S3 implements IPhone {
public void call(String number) {
}
public void message(String number, String text) {
}
}
고수준 모듈 : 어떤 의미 있는 단일 기능을 제공하는 모듈 (Interface, 추상 클래스)
저수준 모듈 : 고수준 모듈의 기능을 구현하기 위해 필요한 하위 기능의 실제 구현 (메인 클래스, 객체)
public class Kid {
private Robot toy;
public void setToy(Robot toy) {
this.toy = toy;
}
public void play() {
System.out.println(toy.toString());
}
}
public class Main{
public static void main(String[] args) {
Robot robot = new Robot();
Kid k = new Kid();
k.setToy(robot);
k.play();
}
}
// Kid는 robot 이라는 구체적인 객체에 의존하고 있음.
// 새로운 장난감이 추가되면 아래와 같이 Kid 클래스를 수정해야 함.
public class Kid {
private Robot toy;
private Lego toy; //레고 추가
// 아이가 가지고 노는 장난감의 종류만큼 Kid 클래스 내에 메서드가 존재해야함.
public void setToy(Robot toy) {
this.toy = toy;
}
public void setToy(Lego toy) {
this.toy = toy;
}
public void play() {
System.out.println(toy.toString());
}
}
// Toy 라는 추상클래스와 의존관계를 맺도록 설계
public class Kid {
private Toy toy;
public void setToy(Toy toy) {
this.toy = toy;
}
public void play() {
System.out.println(toy.toString());
}
}
public class Robot extends Toy {
public String toString() {
return "Robot";
}
}
// 새로운 장난감을 추가하고 싶다면 ?
public class Lego extends Toy {
public String toString() {
return "Lego";
}
}
public class Main{
public static void main(String[] args) {
Toy lego = new Lego();
Kid k = new Kid();
k.setToy(lego);
k.play();
}
}
// Dog 추상클래스 생성
public abstract class Dog {
private String name;
private String type;
public Dog(String name, String type) {
this.name = name;
this.type = type;
}
abstract void howl();
abstract void walk();
public void smell() {
System.out.println(name + "이 냄새를 맡습니다.");
}
public String getName() {
return name;
}
public String getType() {
return type;
}
}
public class WelshCorgi extends Dog {
public WelshCorgi(String name) {
super(name, "웰시코기");
}
@Override
void howl() {
System.out.println("왈왈! 나는야 " + getName() + ", " + getType());
}
@Override
void walk() {
System.out.println("터벅터벅");
}
@Override
public void smell() {
super.smell();
System.out.println("킁킁");
}
}
public class Maltese extends Dog {
public Maltese(String name) {
super(name, "말티즈");
}
@Override
void howl() {
System.out.println("멍멍! 나는야 " + getName() + ", " + getType());
}
@Override
void walk() {
System.out.println("총총");
}
@Override
public void smell() {
super.smell();
System.out.println("스읍스읍");
}
}
- 은닉화 : 객체의 세부 내용이 외부에 드러나지 않아 외부에서 데이터를 직접 접근하는 것을 방지한다.
public : 하위 클래스와 인스턴스에서 접근 가능static : 클래스와 외부에서 접근 가능. 인스턴스에서는 접근 불가private : 클래스 본인만 접근 가능, 하위클래스, 인스턴스 접근 불가protected : 클래스 본인, 하위 클래스에서 접근 가능, 인스턴스 접근 불가 class Person {
public String name;
public int age;
}
public class Main {
public static void main(String[] args) {
Person person = new Person();
// 필드에 직접 접근하여 값을 설정
person.name = "Alice";
person.age = 25;
// 필드에 직접 접근하여 값을 출력
System.out.println("Name: " + person.name);
System.out.println("Age: " + person.age);
// 유효하지 않은 값도 직접 설정 가능
person.age = -5;
System.out.println("Invalid Age: " + person.age);
}
}
// Person 클래스에 이름, 나이와 같은 속성과 get, set 메서드를 묶어 캡슐화
class Person {
// 접근 제한자를 통해 은닉화
private String name;
private int age;
// 이름을 가져오는 메서드
public String getName() {
return name;
}
// 이름을 설정하는 메서드
public void setName(String name) {
this.name = name;
}
// 나이를 가져오는 메서드
public int getAge() {
return age;
}
// 나이를 설정하는 메서드
public void setAge(int age) {
if (age > 0) {
this.age = age;
} else {
System.out.println("유효하지 않은 나이입니다.");
}
}
}
public class Main {
public static void main(String[] args) {
Person person = new Person();
// 메서드를 통해 값을 설정
person.setName("Alice");
person.setAge(25);
// 메서드를 통해 값을 출력
System.out.println("Name: " + person.getName());
System.out.println("Age: " + person.getAge());
// 유효하지 않은 값 설정 시도
person.setAge(-5); // "유효하지 않은 나이입니다." 메시지 출력
System.out.println("Age after invalid attempt: " + person.getAge());
}
}
class Animal {
private String name;
public void setName(String name) {
this.name = name;
}
public abstract void cry(){
System.out.println("ㅜㅜㅜ");
}
}
// Cat 클래스는 Animal 클래스를 상속받았음.
// 따라서 모든 코드를 물려 받아 중복코드를 줄이고 수정하고 싶은 cry 메서드만 재정의하였다.
class Cat extends Animal {
public void cry() {
System.out.println("냐옹냐옹!");
}
}
같은 요청으로부터 응답이 객체의 타입에 따라 나르게 나타나는 것.
어떤 객체의 속성이나 기능이 상황에 따라 여러 형태로 변할 수 있다는 것.
메서드 오버라이딩/오버로딩
class Parent {
void display() {
System.out.println("부모 클래스의 display() 메소드입니다.");
}
}
class Child extends Parent {
void display() {
System.out.println("자식 클래스의 display() 메소드입니다.");
}
}
void display(int num1){System.out.println(num1)}
void display(int num1, int num2){System.out.println(num1+num2)}
void display(int num1, double num2){System.out.println(num1+num2)}
개발 유연성, 코드 재사용성을 제고시킬 수 있음.
상위 객체의 타입으로 하위 객체를 참조할 수 있음.
객체 호출 - 스택 저장 - 지역 변수 저장 - 코드 실행 - 반환 값 return - 호출 정보 제거 과정을 거쳐야하기 때문에 절차지향보다 느림응집도와 결합도
응집도는 모듈에 포함된 내부 요소들이 연관되어 있는 정도를 나타낸다.
결합도는 의존성의 정도를 나타내며 다른 모듈에 대해 얼마나 많은 정보를 갖고 있는지 나타내는 정도다.