[JAVA] 객체지향 프로그래밍(OOP)

Posted by 김성철

JAVA - 객체지향 프로그래밍(OOP)

OOP(Object-Oriented Programming)

OOP는 객체지향 프로그래밍이라고도 불리며, 프로그래밍 패러다임 중 하나입니다.  

함수형 프로그래밍(functional programming)과 객체지향 프로그래밍(Object-Oriented Programming)의 차이

- 상태와 변경 관리  
	객체지향 프로그래밍은 객체 간의 상호작용과 객체의 상태를 중요시합니다.  
	객체의 상태는 객체의 메서드를 통해 변경되며, 객체의 상태를 중심으로 프로그래밍합니다.  
  
	함수형 프로그래밍은 상태 변경을 허용하지 않습니다.  
	함수형 프로그래밍에서는 함수를 이용하여 상태를 변경하는 것이 아니라, 입력값에 따라 출력값을 반환하도록 구현합니다.  
  
- 코드 구성 방식  
	객체지향 프로그래밍은 객체들의 상호작용을 중심으로 코드를 구성합니다. 코드는 객체의 상태와 메서드를 이용하여 작성됩니다. 객체지향 프로그래밍에서는 클래스를 작성하고, 이를 이용하여 객체를 생성하고 관리합니다.  
	함수형 프로그래밍에서는 함수를 중심으로 코드를 구성합니다. 함수형 프로그래밍에서는 함수를 일급 객체로 다루며, 함수를 이용하여 프로그램을 작성합니다.  
  
- 병렬성 처리  
	함수형 프로그래밍에서는 상태 변경이 없기 때문에 여러 개의 스레드에서 동시에 수행될 수 있습니다.  
	함수형 프로그래밍은 입력값에 대한 출력값만 계산하면 되기 때문에 스레드 간 동기화에 대한 부담이 적습니다.  
	객체지향 프로그래밍에서는 객체 상태를 변경하므로 여러 개의 스레드에서 동시에 실행될 경우, 상태 변경에 대한 문제가 발생할 수 있습니다.  
  
- 부작용  
	객체지향 프로그래밍은 메소드의 호출로 인해 객체의 상태가 변경될 수 있으므로 부작용(side effect)이 발생할 수 있습니다. 반면에 함수형 프로그래밍은 순수 함수를 중심으로 프로그래밍하므로 부작용이 발생하지 않습니다.  
  
- 데이터 처리 방식  
	객체지향 프로그래밍은 데이터와 해당 데이터를 처리하는 메소드를 하나의 객체로 묶어서 처리합니다.  
	반면에 함수형 프로그래밍은 데이터와 데이터를 처리하는 함수를 분리하여 처리합니다.  
  
- 예외 처리  
	객체지향 프로그래밍은 예외 처리를 try-catch 문으로 처리합니다.  
	반면에 함수형 프로그래밍에서는 예외를 발생시키지 않는 방식으로 예외 처리를 합니다. 대신, 함수의 반환값에 에러 상태를 포함시켜서 처리합니다. 예를 들면, Option 타입이나 Either 타입 등을 사용합니다.  
  
- 동시성 처리  
	함수형 프로그래밍은 불변성(immutability)을 중요시하기 때문에 스레드 세이프(thread-safe)하며, 동시성 처리가 용이합니다.  
	반면에 객체지향 프로그래밍에서는 객체의 상태 변경으로 인한 동시성 문제가 발생할 수 있으므로, 별도의 동기화(synchronization) 처리가 필요합니다.  
  
- 코드의 간결성  
	함수형 프로그래밍은 순수 함수를 사용하기 때문에 코드의 간결성이 높고, 코드의 재사용성이 높습니다.  
	반면에 객체지향 프로그래밍에서는 코드의 구현이 객체의 상태와 메소드로 나눠져 있기 때문에 코드의 길이가 길어질 가능성이 있습니다.  
  
- 추상화 단위  
	객체지향 프로그래밍에서는 객체를 중심으로 추상화합니다. 객체의 상태와 메서드를 추상화하고, 이를 클래스로 정의하여 객체를 생성하고 관리합니다.  
	함수형 프로그래밍에서는 함수를 추상화합니다. 입력값과 출력값을 추상화하고, 이를 함수로 정의하여 재사용합니다.  

OOP의 주요 특징

OOP의 특징들은 코드의 재사용성과 유지 보수성을 높여줌으로써 개발 생산성을 높이고, 코드의 가독성을 높여줍니다.  
OOP는 현재까지도 널리 사용되는 프로그래밍 패러다임 중 하나입니다.  
  
- 캡슐화(Encapsulation)  
	데이터와 해당 데이터를 처리하는 메소드들을 하나의 클래스로 묶어 캡슐화합니다.  
	캡슐화된 객체의 내부 구조는 외부에 노출되지 않으며, 외부에서는 객체의 메소드를 호출하여 내부 데이터를 처리합니다.  
	이로써 객체의 내부 구조가 변경되더라도 외부에 영향을 미치지 않고, 코드의 유지 보수성과 재사용성을 높일 수 있습니다.  
  
- 상속(Inheritance)  
	기존의 클래스를 재사용하여 새로운 클래스를 만드는 것을 상속이라고 합니다.  
	상속을 통해 기존 클래스의 속성과 메소드를 재사용하면서 새로운 기능을 추가하거나 기존 기능을 변경할 수 있습니다.  
	이로써 코드의 중복을 줄이고, 코드의 재사용성을 높일 수 있습니다.  
  
- 다형성(Polymorphism)  
	하나의 객체가 여러 가지 형태를 가질 수 있는 것을 다형성이라고 합니다.  
	다형성을 통해 객체의 타입 변환이 가능하며, 이는 코드의 유연성을 높이고 확장성을 높일 수 있습니다.  
	다형성은 오버로딩(Overloading)과 오버라이딩(Overriding)을 통해 구현됩니다.  
  
- 추상화(Abstraction)  
	객체의 공통적인 특징을 추출하여 하나의 추상적인 개념으로 표현하는 것을 추상화라고 합니다.  
	추상화를 통해 객체의 복잡성을 단순화하고, 개발자가 객체를 더 쉽게 이해할 수 있습니다.  
	추상화는 인터페이스(Interface)와 추상 클래스(Abstract Class)를 통해 구현됩니다.  

캡슐화

캡슐화(Encapsulation)는 객체 지향 프로그래밍(OOP)에서 사용되는 개념 중 하나로,  
데이터와 그 데이터를 처리하는 함수를 하나로 묶어서 외부에서 접근하지 못하도록 접근 제어를 하는 것을 말합니다.  
  
쉽게 말해 객체의 내부 데이터와 함수를 외부에서 직접 접근하는 것이 불가능하도록 만들어 정보를 보호하고,  
객체가 제공하는 인터페이스를 통해서만 접근할 수 있도록 제한하는 것입니다. 이는 객체의 내부 구현을 숨기고 외부에서 객체를 사용하는 것을 간단하게 만들어 줍니다.  
  
캡슐화를 통해 객체의 내부 구현을 변경해도 외부에서는 영향을 받지 않으므로, 유연성과 유지보수성을 높일 수 있습니다.  
객체의 데이터를 외부에서 직접 수정하는 것을 막아 데이터의 일관성을 유지할 수 있으며, 객체 지향 프로그래밍에서 중요한 개념 중 하나로 매우 중요합니다.  

상속

상속(Inheritance)은 객체 지향 프로그래밍(OOP)에서 사용되는 개념 중 하나로, 기존에 정의된 클래스를 바탕으로 새로운 클래스를 정의하는 것입니다.  
이를 통해 코드의 재사용성과 확장성을 높일 수 있습니다.  
  
상속을 이용하면 부모 클래스(Parent Class)의 모든 속성과 메서드를 자식 클래스(Child Class)가 물려받아 사용할 수 있습니다.  
자식 클래스는 부모 클래스의 모든 속성과 메서드를 그대로 사용할 수 있으며, 필요한 경우에는 추가적인 속성과 메서드를 정의할 수도 있습니다.  
  
상속은 코드의 중복을 줄이고 유지보수를 용이하게 해주며, 객체 지향 프로그래밍의 다형성(Polymorphism)을 구현하는데 매우 중요한 역할을 합니다.  
부모 클래스에서 정의된 메서드를 자식 클래스에서 재정의(Override)하여 자식 클래스에 맞게 구현할 수 있습니다.  
이렇게 되면 같은 메서드 이름을 가진 부모 클래스와 자식 클래스의 메서드가 각각 다른 기능을 수행하게 되어 다형성을 구현할 수 있습니다.  
  
하지만 상속은 오용될 경우에는 코드의 복잡도를 높이고 유지보수성을 저하시킬 수 있습니다.  
따라서 상속을 사용할 때는 상속의 목적을 명확하게 파악하고, 적절한 상속 계층을 구성하여야 합니다.  

다형성

다형성(Polymorphism)은 하나의 변수명, 메서드명 또는 객체가 여러 가지 형태 또는 타입을 가질 수 있는 능력을 말합니다.  
이를 통해 코드의 재사용성과 유지보수성을 높일 수 있습니다.  
  
예를 들어, 동물(Animal) 클래스가 있고, 이를 상속받은 각각의 하위 클래스인 개(Dog), 고양이(Cat), 새(Bird) 클래스가 있다고 가정해보겠습니다.  
이 경우, Animal 타입의 변수에 Dog, Cat, Bird 객체를 모두 대입할 수 있습니다.  
이렇게 하면 Animal 타입의 변수로 Dog, Cat, Bird 객체에 접근할 수 있으며, 각 클래스에서 재정의된 메서드를 호출할 수 있습니다.  
  
이러한 다형성은 코드의 유연성을 높여주며, 다양한 객체들을 하나의 타입으로 묶어 처리할 수 있도록 해줍니다.  
이를 위해 추상 클래스, 인터페이스 등의 개념이 사용됩니다.  
추상 클래스와 인터페이스를 통해 공통적인 기능을 가진 객체들을 묶어 하나의 타입으로 처리할 수 있으며,  
각 객체는 해당 기능을 구현한 자신만의 메서드를 가지고 있습니다. 이렇게 함으로써 다형성을 구현할 수 있습니다.  
  
또한 다형성을 설명할때 오버로딩과 오버라이딩이 많이 언급됩니다.  
  
- 오버로딩(Overloading)  
	오버로딩은 하나의 클래스 내에서 같은 이름의 메소드를 여러 개 정의하는 것입니다.  
	이때 메소드의 파라미터 타입, 개수, 순서가 서로 다르면 서로 다른 메소드로 인식됩니다.  
	즉, 같은 이름의 메소드지만 파라미터에 따라 다른 동작을 수행할 수 있도록 하는 것입니다.  
	오버로딩은 다형성 중 정적 다형성(Static Polymorphism)에 해당합니다.  
	ex) 예시코드  
	======================================================================================================  
	public class Calculator {  
		public int add(int a, int b) {  
			return a + b;  
		}  
  
		public double add(double a, double b) {  
			return a + b;  
		}  
	}  
	======================================================================================================  
  
- 오버라이딩(Overriding)  
	오버라이딩은 부모 클래스에서 정의된 메소드를 자식 클래스에서 재정의하는 것입니다.  
	즉, 자식 클래스에서 부모 클래스의 메소드를 덮어쓰는 것입니다.  
	이때 메소드의 시그니처(이름, 파라미터 타입, 반환 타입)는 부모 클래스의 메소드와 동일해야 합니다.  
	오버라이딩은 다형성 중 동적 다형성(Dynamic Polymorphism)에 해당합니다.  
	ex) 예시코드  
	======================================================================================================  
	class Animal {  
		public void makeSound() {  
			System.out.println("Animal is making a sound");  
		}  
	}  
  
	class Dog extends Animal {  
		@Override  
		public void makeSound() {  
			System.out.println("Dog is barking");  
		}  
	}  
  
	class Cat extends Animal {  
		@Override  
		public void makeSound() {  
			System.out.println("Cat is meowing");  
		}  
	}  
	======================================================================================================  

추상화

객체 지향 프로그래밍에서 추상화(Abstraciton)는 객체들의 공통점을 추출하여 간결한 모델을 만들어 내는 프로세스입니다.  
이를 통해 복잡한 문제를 해결하기 위해 필요한 정보만 추출하여 이해하기 쉽고 관리하기 용이한 형태로 만들어집니다.  
  
추상화는 객체의 공통점을 파악하여 이를 기반으로 클래스를 생성하고, 객체를 생성하는 과정에서 해당 클래스를 상속받아 객체를 생성합니다.  
이때, 추상화를 통해 만들어진 클래스는 실제 객체를 생성하는데 사용되는 일종의 템플릿 역할을 합니다.  
  
추상화는 또한 객체 지향 설계 원칙 중 하나인 인터페이스를 통해 구현될 수도 있습니다.  
인터페이스는 추상화된 메서드를 정의하고 이를 구현한 클래스에서 실제 로직을 작성하도록 하는 역할을 합니다.  
이를 통해 유연하고 확장성 있는 코드를 작성할 수 있습니다.