
프로그래밍 패러다임Software Engineering2023. 11. 21. 22:39
Table of Contents
📋 프로그래밍 패러다임
📌 프로그래밍 패러다임이란?
- 프로그래머가 코드를 어떻게 작성해야 할 지 결정하는 역할
- 프로그래밍 패러다임을 통해 여러 방식으로 코드를 바라보게 되고, 이를 바탕으로 구현하게 된다.
- 명령형 프로그래밍 : 무엇(What)보다 어떻게(How)를 설명하는 패러다임
- 절차지향 프로그래밍 : 수행되어야 할 기능을 순차적인 처리 과정으로 진행하는 방식
- 객체지향 프로그래밍 : 객체들의 집합으로 프로그램의 상호작용으로 진행하는 방식
- 선언형 프로그래밍 : 어떻게(How)보다 무엇(What)을 설명하는 패러다임
- 함수형 프로그래밍 : 순수 함수를 조합하여 SW를 만드는 방식
📌 명령형 프로그래밍
✅ 절차지향 프로그래밍 (Procedural Programming)
- 순차적 처리를 중요하게 생각
- 프로그램 전체가 유기적으로 연결되도록 만드는 기법
- 장점
- 가독성이 좋다
- 코드의 단위화
- 컴퓨터의 실제 처리 구조와 비슷하여 실행 속도가 빠르다
- 단점
- 각 코드가 순서에 따라 실행되어 변경/유지보수/분석이 어렵다.
- 변수, 상수 등 값들을 관리하는 자료형과 해당 자료형을 사용하는 함수가 분리되어 사용된다.
- 대표적인 절차지향 프로그래밍 언어 : C, Pascal
✅ 객체지향 프로그래밍 (Object Oriented Programming)
- 모든 데이터를 객체(Object)로 취급하여 객체가 처리 요청을 받았을 때 객체 내부 기능을 활용하여 해당 요청을 처리하는 방식
- 모든 객체가 내부 자료형(Field)와 함수(Method)로 구성된 구조
객체(Object) : 객체지향 프로그래밍의 가장 기본적인 단위이자 시작점
- 모든 실재하는 대상
- 우리가 보고 느끼고 인지할 수 있는 모든 것
- 모든 사건은 객체들 간의 상호작용으로 발생한다.
- 속성 / 기능 / 변수 / 함수
- 장점
- 프로그램을 보다 유연하고 변경이 용이하게 만든다.
- 코드의 재사용 (유지보수에 좋다)
- 분석과 설계의 전환이 쉽다.
- 단점
- 처리 속도가 상대적으로 빠르다
- 설계에 많은 시간이 소요된다.
- 대표적인 객체지향 프로그래밍 언어 : Java, Python, C++
✅ 객체지향 프로그래밍의 4가지 특징
- 추상화 (Abstraction) : 사물이나 표상을 어떤 성질, 공통성, 본질에 착안하여 그것을 추출하여 파악하는 것
- 불필요한 세부 사항은 제거하고 가장 본질적이고 공통적인 부분만을 추출하여 표현
- 객체의 공통적인 속성과 기능을 추출하여 정의하는 것
- 대표적인 객체 지향 프로그래밍, Java에서 추상화를 구현할 수 있는 무법 요소 : 추상 클래스(abstract class), 인터페이스(interface)
// Vehicle 인터페이스
public interface Vehicle {
public abstract void start();
void moveForward(); // public abstract 키워드는 생략이 가능하다.
void moveBackward();
}
// Car 클래스
// 이동 수단을 구체화한 자동차 클래스
public class Car implements Vehicle {
@Override
public void moveForward() {
System.out.println("자동차가 앞으로 전진합니다!");
}
@Override
public void moveBackward() {
System.out.println("자동차가 뒤로 후진합니다!");
}
}
// MotorBike 클래스
public class MotorBike implements Vehicle {
@Override
public void moveForward() {
System.out.println("오토바이가 앞으로 전진합니다!");
}
@Override
public void moveBackward() {
System.out.println("오토바이가 뒤로 후진합니다!");
}
}
- 위처럼 인터페이스에 정의한 역할을 각 클래스의 맥락에 맞게 구현한다.
- 이것을
역할과 구현의 분리
라고 한다.
- 객체지향 프로그래밍에서는 보다 유연하고 변경에 열려있는 프로그램을 설계하기 위해 역할과 구현을 분리한다.
- 캡슐화 (Encapsulation) : 서로 연관있는 속성과 기능들을 하나의 캡슐로 만들어 데이터를 외부로부터 보호하는 것
- 데이터 보호 (Data Protection) : 외부로부터 클래스에 정의된 속성과 기능들을 보호
- 데이터 은닉 (Data Hiding) : 내부의 동작을 감추고 외부에는 필요한 부분만 노출
- 외부로부터 클래스에 정의된 속성과 기능들을 보호하고, 필요한 부분만 외부로 노출될 수 있도록 하여 각 객체 고유의 독립성과 책임 영역을 안전하게 지키고자 하는 목적
- 자바에서 캡슐화를 구현하기 위한 두 기능 : 접근제어자, getter/setter 메서드
접근 제어자 | 클래스 내 | 패키지 내 | 다른 패키지의 하위 클래스 | 패키지 외 | 설명 |
---|---|---|---|---|---|
private | O | X | X | X | 동일 클래스 내에서만 접근 가능 |
default | O | O | X | X | 동일 패키지 내에서만 접근 가능 |
protected | O | O | O | X | 동일 패키지 + 다른 패키지의 하위 클래스에서 접근 가능 |
public | O | O | O | O | 접근 제한 없음 |
- 상속성 (Inheritance) : 기존의 클래스를 재활용하여 새로운 클래스를 작성하는 문법 요소
- 상위 클래스로부터 확장된 여러 하위 클래스들이 모두 상위 클래스의 속성과 기능을 사용할 수 있다.
- 딱 한번 정의하고 간편하게 재사용이 가능하여 반복적인 코드를 최소화하고 공유하는 속성과 기능에 간편하게 접근가능하다.
public class Car {
String model;
String color;
int wheels;
// Car 클래스의 고유 속성
boolean isConvertible;
void moveForward() {
System.out.println("앞으로 전진!");
}
void moveBackward() {
System.out.println("뒤로 후진!");
}
// Car 클래스의 고유 기능
void openWindow() {
System.out.println("모든 창문을 연다!");
}
}
public class MotorBike {
String model;
String color;
int wheels;
// MotorBike 클래스 고유 속성
boolean isRaceable;
void moveForward() {
System.out.println("앞으로 전진!");
}
void moveBackward() {
System.out.println("뒤로 후진!");
}
// MotorBike 클래스 고유 기능
public void stunt() {
System.out.println("묘기 부리기!");
}
}
- 위 코드에서는 다른 두 클래스가 반복적으로 사용되는 속성과 기능들이 있다.
- 또한 하나의 코드에서 변경이 일어나면 다른 곳에서도 수정이 필요하다.
// 추상화를 통해 상위 클래스 정의
public class Vehicle {
String model;
String color;
int wheels;
void moveForward() {
System.out.println("앞으로 전진!");
}
void moveBackward() {
System.out.println("뒤로 후진!");
}
}
// Car 클래스
public class Car extends Vehicle {
boolean isConvertible;
void openWindow() {
System.out.println("모든 창문을 열다!");
}
}
// MotorBike 클래스
public class MotorBike extends Vehicle {
boolean isRaceable;
// 메서드 오버라이딩 -> 기능 재정의
@Override
void moveForward() {
System.out.println("오토바이가 앞으로 전진합니다!");
}
public void stunt() {
System.out.println("묘기를 부립니다!");
}
}
- 위 코드에서 Car과 MotorBike의 공통적인 속성과 기능을 추상화하여 Vehicle 이라는 상위 클래스에 정의하고, extends 키워드를 통해 하위 클래스로 확장하였다.
- MotorBike에서는 클래스의 맥락에 맞게 메서드 오버라이딩 기능도 사용하여 내용을 재정의할 수 있다.
- 상속
- extends 키워드
- 상위 클래스의 속성과 기능들을 하위 클래스에서 그대로 받아 사용하거나 오버라이딩을 통해 선택적으로 재정의하여 사용
- 구현
- implements 키워드
- 반드시 인터페이스에 정의된 추상 메서드의 내용이 하위 클래스에서 정의되어야 한다.
- 다형성 (Polymorphism) : 어떤 객체의 속성이나 기능이 상황에 따라 여러 형태를 가질 수 있는 성질
- 어떤 객체의 속성이나 기능이 그 맥락에 따라 다른 역할을 수행할 수 있는 객체지향의 특성을 의미
- ex> 메서드 오버라이딩, 메서드 오버로딩
- 한 타입의 참조변수를 통해 여러 타입의 객체를 참조할 수 있도록 만든 것
- 상위 클래스 타입의 참조변수로 하위 클래스의 객체를 참조할 수 있도록 하는 것
// Main 클래스
public class Main {
public static void main(String[] args) {
// 원래 사용했던 객체 생성 방식
Car car = new Car();
MotorBike motorBike = new MotorBike();
// 다형성을 활용한 객체 생성 방식
Vehicle car2 = new Car();
}
}
- 상위클래스 타입의 참조변수로 하위클래스 객체를 참조
public class Main {
public static void main(String[] args) {
// 상위 클래스 타입의 객체 배열 생성
Vehicle vehicles[] = new Vehicle[2];
vehicles[0] = new Car();
vehicles[1] = new MotorBike();
for(Vehicle vehicle : vehicles)
System.out.println(vehicle.getClass());
}
}
------------- 출력 ------------
class Car
class MotorBike
- 하나의 타입만으로 여러 가지 타입의 객체를 참조했다.
// Driver 클래스
public class Driver {
void drive(Car car) {
car.moveForward();
car.moveBackward();
}
void drive(MotorBike motorBike) {
motorBike.moveForward();
motorBike.moveBackward();
}
}
// main 클래스
public class Main {
public static void main(String[] args) {
Car car = new Car();
MotorBike motorBike = new MotorBike();
Driver driver = new Driver();
driver.drive(car);
driver.drive(motorBike);
}
}
- Driver 클래스는 Car 클래스와 MotorBike 클래스에 의존한다.
- 객체간 결합도가 높다.
- 이 상태는 객체 지향적 설계에 불리하다.
- 이러한 문제를 해결하기 위해 등장한 개념이 의존관계 주입(dependency injection)이다.
- 이는 스프링 프레임워크의 핵심적인 개념이다.
- 이 상태는 객체 지향적 설계에 불리하다.
✅ 객체지향 프로그래밍의 5 가지 설계 원칙 : SOLID
- SOLID : 객체 지향 프로그래밍을 설계하면서 지켜야 할 다섯 가지 설계 원칙이다.
- SRP, OCP, LSP, DIP, ISP
- 단일 책임 원칙 (SRP, Single Responsibility Principle)
- 모듈이 변경되는 이유가 한가지이어야 한다.
- 해당 모듈이 여러 대상 또는 액터들에 대해 책임을 가져서는 안되고, 오직 하나의 액터에 대해서면 책임을 져야 한다.
- 단일 책임 원칙을 지키면 변경이 필요할 때 수정할 대상이 명확해진다.
- 단일 책임 원칙을 적용하여 적절하게 책임과 관심이 다른 코드를 분리하고 서로 영향을 주지 않도록 추상화하여 애플리케이션 변화에 손쉽게 대응할 수 있다.
- 개방 폐쇄 원칙 (OCP, Open-Closed Principle)
- 확장에 열려있고 수정에 닫혀있어야 한다.
- 확장에 열려있다 : 요구사항이 변경될 때 새 동작을 추가하여 애플리케이션의 기능을 확장할 수 있다.
- 수정에 닫혀있다 : 기존 코드를 수정하지 않고 애플리케이션의 동작을 추가하거나 변경할 수 있다.
- 개방-폐쇄 원칙을 지키기 위해서는 추상화에 의존해야 한다.
- 추상화를 통해 변하지 않는 부분만 남김으로써 기능을 구체화하고 확장할 수 있다.
- 변하지 않는 부분은 고정하고 변하는 부분을 생략하여 추상화함으로써 변경이 필요한 경우에 생략된 부분을 수정하여 개방-폐쇄 원칙을 지킬 수 있다.
- 개방-폐쇄 원칙이 본질적으로 말하는 것은 추상화이고, 이는 결국 런타임 의존성과 컴파일타임 의존성에 대한 이야기이다.
- 런타임 의존성 : 애플리케이션 실행 시점에서의 객체들의 관계
- 컴파일타임 의존성 : 코드에 표현된 클래스들의 관계를 의미한다.
관련 설명 : https://mangkyu.tistory.com/226
- 추상화를 통해 변하는 것을 숨기고 변하지 않는것에 의존한다면, 기존 코드 및 클래스를 수정하지 않고 애플리케이션의 확장이 가능하다.
- 이 것이 개방-폐쇄 원칙의 의미이다.
- 인터페이스 분리 원칙 (ISP, Interface Segregation Principle)
- 목적과 관심이 각자 다른 클라이언트가 있다면 인터페이스를 통해 적절히 분리한다.
- 클라이언트의 목적과 용도에 적합한 인터페이스만을 제공하는 것
- 인터페이스 분리 원칙을 준수함으로써 모든 클라이언트가 자신의 관심에 맞는 퍼블릭 인터페이스만 접근하여 불필요한 간섭을 최소화할 수 있다.
- 리스코프 치환 원칙 (LSP, Liskov Subsitution Principle)
- 하위 타입은 상위 타입을 대체할 수 있어야 한다.
- 해당 객체를 사용하는 클라이언트는 상위 타입이 하위 타입으로 변경되어도 차이점을 인식하지 못한 채 상위 타입의 퍼블릭 인터페이스를 통해 서브 클래스를 사용할 수 있어야 한다.
- 자식 클래스가 부모 클래스를 대체하기 위해 부모 클래스에 대한 클라이언트의 가정을 준수해야 한다는 것을 강조한다.
- 대체 가능성을 결정해야 하는 것은 해당 객체를 이용하는 클라이언트라는 것을 반드시 잊지 말아야 한다.
- 의존 역전 원칙 (DIP, Dependency Inversion Principle)
- 고수준 모듈은 저수준 모듈의 구현에 의존해서는 안되며, 저수준 모듈이 고수준 모듈에 의존해야 한다는 것.
- 고수준 모듈 : 입력과 출력으로부터 먼, 비즈니스와 관련된 추상화된 모듈
- 저수준 모듈 : 입력과 출력으로부터 가까운, HTTP/DB/Cache와 관련된 구현 모듈
- 결국 비즈니스와 관련된 부분이 세부 사항에는 의존하지 않는 설계 원칙을 말한다.
- 결국 위 다섯가지 객체지향 설계원칙인 SOLID는 추상화과 다형성이다.
- 구체 클래스에 의존하지 않고 추상 클래스 또는 인터페이스에 의존함으로써 유연하고 확장가능한 애플리케이션을 만들 수 있는 것이다.
📌 선언형 프로그래밍
✅ 함수형 프로그래밍 (Functional Programming)
- 순수 함수를 사용하여 상태를 제어하기 보다 빠르게 처리하는 것에 초점을 둔 방법론
- 실행 순서를 지정할 필요 없다. => 비절차형 프로그래밍
- 장점
- 코드에서 프로그래밍에 영향을 미치는 영역과 순수한 영역을 최대한 분리
- 코드의 가독성이 높고 유지보수가 좋다
- 테스트가 쉽다
- 단점
- 외부 데이터 또는 내부 데이터의 상태를 조작할 수 없다.
- 대표적인 함수형 프로그래밍 언어 : Haskell, OCamal, Lisp, Clojure, Erlang
📌 각 패러다임의 비교
✅ 절차지향 프로그래밍 vs 객체지향 프로그래밍
- 절차지향 프로그래밍의 부족한 점을 객체지향 프로그래밍이 보완한다.
- 두 방식 모두 함수가 있지만, 객체지향 프로그래밍에만 '객체' 라는 개념이 존재한다.
- 절차지향 프로그래밍은 데이터 중심, 객체지향 프로그래밍은 기능 중심의 방법론이다.
- 둘은 서로 반대의 방식이 아니다. 다른 방식이다.
✅ 객체지향 프로그래밍 vs 함수형 프로그래밍
- 객체지향 프로그래밍에서는 클래스가 일급 객체가 되지만, 함수형 프로그래밍에서는 함수가 일급 객체가 된다.
- 객체지향 프로그래밍에서는 프로그램을 상호작용하는 객체들의 집합으로 보지만, 함수형 프로그래밍에서는 프로그램을 상태값을 지니지 않는 함수들의 연속으로 볼 수 있다.
일급 객체 (First-Class Object) : 프로그래밍 언어에서 특별한 권한 없이 아래 조건을 충족하는 객체
- 변수나 데이터 구조 안에 담을 수 있어야 한다.
- 함수의 인자로 전달할 수 있어야 한다.
- 함수의 반환 값으로 사용할 수 있어야 한다.
일급 함수 (First-Class Function) : 함수형 프로그래밍에서 함수를 변수에 저장하고 함수를 함수의 인자로 전달하거나 함수를 함수의 반환값으로 사용할 수 있는 것.
- 객체지향 프로그래밍
- 클래스 디자인과 객체들의 관계를 중심으로 코드 작성
- 상태, 멤버변수, 메서드 등이 긴밀한 관계를 가짐
- 멤버변수가 어떤 상태를 가지고 있는지에 따라 결과가 달라진다.
- 함수형 프로그래밍
- 값의 연산 및 결과 도출 중심으로 코드 작성
- 함수는 인자로 받은 값을 별도로 저장하지 않고, 간결한 과정으로 처리하고 매핑하는데 목적을 가진다.
📚 출처
- https://dev-jwblog.tistory.com/86
- chat gpt 4
- https://www.codestates.com/blog/content/%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-%ED%8A%B9%EC%A7%95
- https://mangkyu.tistory.com/194
@Seyun. :: 개발할 결심
개발자가 되고 싶어요.