일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
- 투두앱만들기
- typeScript
- 클래스
- Hooks
- Props
- 논리연산자
- 불변성
- 노마드코더
- 프론트엔드
- js
- REACT
- frontend
- 상속
- 추상화
- 자바스트립트
- 객체지향프로그래밍
- CSS
- Fetch
- 타입스크립트
- 패스트캠퍼스
- webdevelopment
- 리액트
- github
- 부트캠프
- 캡슐화
- 웹개발
- JavaScript
- 자바스크립트
- OOP
- Zustand
- Today
- Total
connecting dots
React & TS 9회차 (7/31) | OOP (객체 지향 프로그래밍), OOP의 5대 원칙 본문
React & TS 9회차 (7/31) | OOP (객체 지향 프로그래밍), OOP의 5대 원칙
dearsuhyun 2024. 8. 1. 12:33코드를 작성한다는 것은 ?
기계에 명령을 주는 것 --> 목적을 달성하기 위해서
명령 --> 순서가 중요 !
어떤 목적을 달성하기 위해 명령을 순서대로 나열하여 기계에 전달하는 것 = 알고리즘
프로그래밍 = 알고리즘을 여러 개 짜는 것
명령어 + 데이터 --> 무언가를 하겠죠 ?
예제: 월급을 전송하는 알고리즘을 작성 (은행, 전송 방식, 금액)
* ftp = 큰 파일 전송에 특화된 프로토콜

같은 목적을 수행하는 중복된 코드를 함수 (또는 프로시저)로 묶어서 재활용
cf. 프로시저: 함수와 비슷하지만 값을 반환하지 않는 특징을 지니고 특정 작업을 수행함


조금 더 맥락을 가지고 복잡도가 높을 때 이걸 낮출 수 있는 방법은 없을까 ?
--> 객체지향프로그래밍(OOP)
클래스와 인스턴스

속성만 가지고 있음
▼
메소드도 가짐

속성은 취약한 구조임 (외부에서 이름 바꾸면 의도하지 않았지만 이름이 바뀌게 됨)
모든 속성을 보호하기 위한 '접근제어자' 필요 !
public (기본값)
constructor(생성자)는 무조건 public
private

클래스의 크기가 영원히 커지지 않도록 해야 함
--> 작은 클래스를 만들고 '상속' 개념을 이용해서 복잡성을 높이지 않고 더 큰 소프트웨어를 만들어보자 !

크기는 커지지만 복잡도는 유지하는 방법 !
자식 클래스에서 접근하지 못하게 부모 클래스를 만들고 싶다면 ?
--> private ! 해당 클래스 안에서만 접근 가능
name은 접근 가능하지만 datdOfjoining에는 접근 불가

Getter와 Setter
객체 지향 프로그래밍에서 클래스의 속성을 읽거나 설정할 때 사용하는 메서드
Getter: 객체의 속성 값을 읽어오는 메서드. 속성의 값을 외부에서 읽을 수 있도록 함
Setter: 객체의 속성 값을 설정하는 메서드. 속성의 값을 외부에서 설정할 수 있도록 함

다형성
객체지향 프로그래밍의 중요한 개념 중 하나로,
같은 인터페이스나 상위 클래스에 대해 여러 다른 하위 클래스가 존재할 수 있으며, 각 하위 클래스가 자신만의 방식으로 그 인터페이스나 상위 클래스의 메서드를 구현할 수 있게 하는 것. 이는 코드를 더 유연하고 확장 가능하게 만들어 줌
대표적인 문법: generic(제네릭)
상위 클래스에 대해서 어떤 상위 클래스를 사용하는 코드가 있을 때, 그 코드에 하위 클래스를 넣어도 문제가 없어야 한다 !
interface Animal {
makeSound(): void
}
class Dog implements Animal {
makeSound(): void {
console.log('멍멍')
}
}
class cat implements Animal {
makeSound(): void {
console.log('야옹')
}
}
function playWithAnimal(animal: Animal): void {
animal.makeSound()
}
const myDog = new Dog()
const myCat = new cat()
playWithAnimal(myDog)
playWithAnimal(myCat)
--> 같은 Animal 인터페이스를 구현한 객체들(dog, cat)이 동일한 메소드(makeSound)를 서로 다른 방식으로 구현
--> playWithAnimal 함수는 Animal 타입의 객체를 인자로 받아 동작하므로, Dog 객체나 Cat 객체 모두를 인자로 받아 처리할 수 있음
이는 코드를 더 유연하게 만들어줌 !
캡슐화
메소드 오버라이딩(method overriding)
객체 지향 프로그래밍(OOP)에서 하위 클래스가 상위 클래스의 메서드를 재정의하여 자신만의 기능을 제공하는 것을 의미.
이는 상속과 다형성의 중요한 부분 중 하나 ! 오버라이딩된 메서드는 상위 클래스의 메서드와 이름, 매개변수, 반환 타입이 동일해야 함.
자식이 부모를 상속받은 후, 부모가 가진 메소드를 같은 이름으로 재구현하는 것 (부모의 메소드는 사용되지 않음)
extends VS implements
extneds | implement | |
사용 | 클래스가 다른 클래스를 상속받을 때 사용 | 클래스가 인터페이스 구현할 때 사용 |
특징 | 부모 클래스의 속성과 메소드를 자식 클래스가 상속받아 사용 가능 한 클래스는 하나의 부모 클래스만 상속 가능 |
인터페이스에 정의된 모든 메소드를 클래스가 반드시 구현해야 함 한 클래스는 여러 인터페이스 구현 가능 |
OOP의 5대 원칙
어떤 소프트웨어를 만들더라도 5가지 원칙을 위배하지 마라 !

1. SRP: 단일책임원칙 single responsibility principle **
하나의 클래스는 하나의 기능만 가져야 한다는 원칙
--> 하나의 클래스가 너무 많은 일을 하지 않도록 하여 유지보수와 이해가 쉽도록 함 !
잘못된 예제
UserService 클래스에 3가지 기능이나 정의되어 있음
public class UserService {
public void saveUser(User user) {
// Save user to database
System.out.println("User saved to database: " + user.getName());
}
public void sendWelcomeEmail(User user) {
// Send welcome email to user
System.out.println("Welcome email sent to: " + user.getEmail());
}
public void logUserActivity(User user) {
// Log user activity
System.out.println("Logging activity for user: " + user.getName());
}
}
class User {
private String name;
private String email;
public User(String name, String email) {
this.name = name;
this.email = email;
}
public String getName() {
return name;
}
public String getEmail() {
return email;
}
}
올바른 예제
위 코드에서 뭉쳐있던 기능이 각 클래스로 나뉨
각 클래스는 단일책임원칙을 따르고 userService 클래스가 이를 조합하여 사용자를 등록함
public class UserRepository {
public void saveUser(User user) {
// Save user to database
System.out.println("User saved to database: " + user.getName());
}
}
public class EmailService {
public void sendWelcomeEmail(User user) {
// Send welcome email to user
System.out.println("Welcome email sent to: " + user.getEmail());
}
}
public class UserActivityLogger {
public void logUserActivity(User user) {
// Log user activity
System.out.println("Logging activity for user: " + user.getName());
}
}
class User {
private String name;
private String email;
public User(String name, String email) {
this.name = name;
this.email = email;
}
public String getName() {
return name;
}
public String getEmail() {
return email;
}
}
public class UserService {
private UserRepository userRepository = new UserRepository();
private EmailService emailService = new EmailService();
private UserActivityLogger userActivityLogger = new UserActivityLogger();
public void registerUser(User user) {
userRepository.saveUser(user);
emailService.sendWelcomeEmail(user);
userActivityLogger.logUserActivity(user);
}
}
2. OCP: 개방 폐쇄 원칙 open/close principle **
소프트웨어 모듈이 확장에는 열려 있고, 수정에는 닫혀 있어야 한다는 원칙
--> 새로운 기능을 추가할 수 있지만 기존 코드를 변경하지 말아야 한다는 것
잘못된 예제
새로운 형식을 추가할 때 마다 generateReport 메소드를 수정해야 함
public class ReportGenerator {
public void generateReport(String type) {
if (type.equals("PDF")) {
System.out.println("Generating PDF report...");
} else if (type.equals("HTML")) {
System.out.println("Generating HTML report...");
}
// If we need to add another format, we have to modify this method.
}
}
올바른 예제
각 보고서 형식(PDF, HTML, XML)에 대해 별도의 클래스를 정의하고, 공통 인터페이스(Report)를 구현하여 새로운 보고서 형식을 추가할 때 기존 코드를 수정할 필요 없이 확장할 수 있게 함.
public interface Report {
void generate();
}
public class PDFReport implements Report {
@Override
public void generate() {
System.out.println("Generating PDF report...");
}
}
public class HTMLReport implements Report {
@Override
public void generate() {
System.out.println("Generating HTML report...");
}
}
public class XMLReport implements Report {
@Override
public void generate() {
System.out.println("Generating XML report...");
}
}
public class Main {
public static void main(String[] args) {
Report pdfReport = new PDFReport();
pdfReport.generate(); // Generating PDF report...
Report htmlReport = new HTMLReport();
htmlReport.generate(); // Generating HTML report...
Report xmlReport = new XMLReport();
xmlReport.generate(); // Generating XML report...
}
}
3. LSP: 리스코프 치환 원칙 liskov substitution principle
자식 클래스는 언제나 부모 클래스를 대체할 수 있어야 한다는 원칙
즉, 부모 클래스의 인스턴스를 사용하는 곳에서는 자식 클래스의 인스턴스를 대신 사용해도 프로그램이 정상적으로 작동해야 한다는 뜻
올바른 상속을 위해, 자식 객체의 확장이 부모 객체의 방향을 온전히 따르도록 권고하는 원칙
잘못된 예제
Penguin 클래스는 Bird 클래스를 상속받았지만, Penguin은 fly 메서드를 지원하지 않기 때문에, 부모 클래스의 인스턴스로 대체될 수 없음
// Parent class Bird
public class Bird {
public void fly() {
System.out.println("Bird is flying");
}
}
// Child class Penguin that violates LSP
public class Penguin extends Bird {
@Override
public void fly() {
// Penguins cannot fly
throw new UnsupportedOperationException("Penguins cannot fly");
}
}
public class Main {
public static void main(String[] args) {
Bird bird = new Bird();
bird.fly(); // Bird is flying
Bird penguin = new Penguin();
penguin.fly(); // Throws UnsupportedOperationException
}
}
올바른 예제
• Flyable 인터페이스: 날 수 있는 새를 위한 인터페이스. fly 메서드를 정의
• Bird 클래스: 모든 새의 기본 클래스. eat 메서드를 구현
• Sparrow 클래스: Bird 클래스를 상속받고, Flyable 인터페이스를 구현하여 날 수 있는 새
• Penguin 클래스: Bird 클래스를 상속받지만, Flyable 인터페이스를 구현하지 않아서 날 수 없는 새
// Interface for birds that can fly
public interface Flyable {
void fly();
}
// Base class Bird
public class Bird {
public void eat() {
System.out.println("Bird is eating");
}
}
// Class for a bird that can fly
public class Sparrow extends Bird implements Flyable {
@Override
public void fly() {
System.out.println("Sparrow is flying");
}
}
// Class for a bird that cannot fly
public class Penguin extends Bird {
// Penguins do not implement Flyable
}
public class Main {
public static void main(String[] args) {
Bird sparrow = new Sparrow();
sparrow.eat(); // Bird is eating
((Flyable) sparrow).fly(); // Sparrow is flying
Bird penguin = new Penguin();
penguin.eat(); // Bird is eating
// ((Flyable) penguin).fly(); // Compilation error, Penguin is not Flyable
}
}
4. ISP: 인터페이스 분리 원칙 interface segregation principle
클라이언트가 자신이 사용하지 않는 메서드에 의존하지 않도록 인터페이스를 작고 구체적으로 분리해야 한다는 원칙
하나의 큰 인터페이스보다 여러 개의 작은 인터페이스를 사용하는 것이 더 낫다는 것을 의미
올바른 예제
interface Workable {
work(): void;
}
interface Eatable {
eat(): void;
}
// Workable 인터페이스는 work 메서드만을 정의하고, Eatable 인터페이스는 eat 메서드만을 정의합니다.
// 이렇게 하면 각 인터페이스가 하나의 책임만을 가지게 됩니다.
class HumanWorker implements Workable, Eatable {
work(): void {
console.log('Human working');
}
eat(): void {
console.log('Human eating');
}
}
// HumanWorker 클래스는 Workable과 Eatable 인터페이스를 둘 다 구현합니다.
// 사람은 일을 할 수도 있고, 먹을 수도 있기 때문에 두 인터페이스를 모두 구현하는 것이 맞습니다.
class RobotWorker implements Workable {
work(): void {
console.log('Robot working');
}
}
// RobotWorker 클래스는 Workable 인터페이스만 구현합니다. 로봇은 일만 할 수 있고 먹을 수 없기 때문에 Eatable 인터페이스를 구현할 필요가 없습니다.
// 이는 ISP를 잘 준수하는 예입니다. 로봇은 자신에게 필요 없는 eat 메서드에 의존하지 않습니다.
// 만약 Workable과 Eatable을 하나의 큰 인터페이스로 만들었다면, RobotWorker 클래스는 필요하지 않은 eat 메서드를 구현해야 했을 것입니다.
// 이는 불필요한 코드와 복잡성을 초래하게 됩니다. 따라서 인터페이스를 작은 단위로 분리하는 것이 더 효율적입니다.
5. DIP: 의존성 역전 원칙 dependency inversion principle
고수준 모듈이 저수준 모듈에 의존해서는 안 되고, 둘 다 추상화에 의존해야 한다는 원칙
잘못된 예제
Switch 클래스는 Fan 클래스에 직접 의존하고 있음.
이는 고수준 모듈이 저수준 모듈에 의존하는 형태로, 의존성 역전 원칙을 위반하고 있음
// Low-level class
public class Fan {
public void spin() {
System.out.println("Fan is spinning");
}
public void stop() {
System.out.println("Fan is stopping");
}
}
// High-level class
public class Switch {
private Fan fan;
public Switch(Fan fan) {
this.fan = fan;
}
public void turnOn() {
fan.spin();
}
public void turnOff() {
fan.stop();
}
}
올바른 예제
// Interface for switchable devices
public interface Switchable {
void turnOn();
void turnOff();
}
// Low-level class implementing the interface
public class Fan implements Switchable {
@Override
public void turnOn() {
System.out.println("Fan is spinning");
}
@Override
public void turnOff() {
System.out.println("Fan is stopping");
}
}
// High-level class
public class Switch {
private Switchable device;
public Switch(Switchable device) {
this.device = device;
}
public void turnOn() {
device.turnOn();
}
public void turnOff() {
device.turnOff();
}
}
cf. 애노테이션(annotation)
코드 사이에 주석처럼 쓰이며 특별한 의미, 기능을 수행하도록 하는 기술
프로그램에게 추가적인 정보를 제공해주는 메타데이터(데이터를 위한 데이터)
@Override
메서드가 부모 클래스나 인터페이스의 메서드를 오버라이드하고 있음을 명시
class Animal {
public void makeSound() {
System.out.println("Animal sound");
}
}
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("Woof");
}
}
https://developer.mozilla.org/ko/docs/Learn/JavaScript/Objects/Object-oriented_programming
객체 지향 프로그래밍 - Web 개발 학습하기 | MDN
객체 지향 프로그래밍(OOP)은 Java 및 C++를 비롯한 많은 프로그래밍 언어의 기본이 되는 프로그래밍 패러다임입니다. 이 기사에서는 OOP의 기본 개념에 대한 개요를 제공합니다. 클래스와 인스턴스
developer.mozilla.org
https://www.youtube.com/watch?v=4O6k9GN8FPo