Object Oriented Programming

Object-Oriented
What is Object-Oriented?
Object-oriented programming is a way of developing systems where objects with responsibilities and authorities exchange messages and cooperate to perform necessary functions.
It is widely used and recognized as the most popular programming paradigm, as it effectively decomposes and constructs complex systems and enables them to be easily understood and efficiently managed.
How to be an Objected-Oriented Software?
Does the SW support Encapsulation, Polymorphism, and Class Inheritance?
Is it possible to restrict access to data?
Encapsulation
Hiding the detailed information inside an object, either conceptually or physically, is called encapsulation.
In other words, the main purpose of encapsulation is to achieve information hiding. Information hiding is a technique used to prevent important information about an object from being exposed to the outside world.
The purpose of encapsulation is to create objects that are easy to modify. By restricting access to the internal workings of an object through encapsulation, the coupling between objects can be reduced, making it easier to modify the design.
/** Encapsulation - written in TypeScript **/
class User {
private name: string;
private age: number;
setName(name: string) { // change name which is a Private Variable
this.name = name;
}
getName() { // get name which is a Private Variable
return this.name;
}
setAge(age: number) { // change age which is a Private Variable
this.age = age;
}
getAge() { // get age which is a Private Variable
return this.age;
}
}
const user = new User(); // user instance
user.setName("Siwon");
user.setAge(22);
console.log(user.getName()); // Siwon
console.log(user.getAge()); // 22
console.log(user.name); // Error: name variable is a Private, which is not able to access outside of the User class
For the Private variables like
nameandage, those are only able to access inside theUserclass by using getter & setter.JS uses the closure to define Private variables.
Inheritance
DRY! - Do not Repeat Yourself!
Inheritance is a mechanism that allows a subclass to inherit the characteristics of a pre-defined superclass, which helps to eliminate code duplication and increase code reusability.
In other words, it means that one class can inherit the characteristics (functions, variables, and data) of another class.
By grouping individual classes into inheritance relationships, it becomes easier to understand the structured relationships between classes.
When modifying data and methods, it is possible to maintain consistency throughout the system by modifying only the superclass.
Inheritance is one of the essential features of object-oriented programming because it allows for the reuse of existing classes.
/** Inheritance **/
class Mother { // Mother: Parent Class
constructor(name, age, tech) {
this.name = name;
this.age = age;
this.tech = tech;
}
getTech(){ return this.tech; } /
}
class Child extends Mother{ // Child: Child Class inheriting Mother
constructor(name, age, tech) {
super(name, age, tech); // call constructor of Parent class
}
}
const child = new Child("Siwon", "22", "Node.js");
console.log(child.name); // Siwon
console.log(child.age); // 22
console.log(child.getTech()); // call getTech from Parent class: Node.js
Abstraction
Abstraction refers to the process of gathering commonalities among objects and declaring them as a new concept. It involves eliminating unnecessary details and focusing only on important and common attributes of objects in order to create a model.
Abstraction is the process of identifying common features of objects and removing unnecessary ones.
It allows us to see the structure and components of a system visually before building it and create similar models for various tests.
Abstraction enables us to write code using only the interfaces that are exposed to the outside, without having to worry about the complex internal implementation.
When designing a class, we can model the functionality that can be commonly bundled through abstraction → abstract class → interface, so that it can be extended through polymorphism in the future.
An interface is a class definition that only defines methods and properties, requiring that the implementation of the declared properties or methods is forced to maintain consistency in the code.
/** Abstraction - written in TypeScript **/
interface Human {
name: string;
setName(name);
getName();
}
// if any of inherited properties or methods from the interface exist, it occurs error
class Employee implements Human {
constructor(public name: string) { }
setName(name) { this.name = name; }
getName() { return this.name; }
}
const employee = new Employee("");
employee.setName("Siwon"); //
console.log(employee.getName());
- It is required to define the interface's properties and methods that the Class uses.
Polymorphism

Polymorphism refers to the concept of an object or class being able to take on different forms or shapes, based on its unique characteristics when performing operations.
In other words, polymorphism is the idea that the same method name can be used, but the method is implemented differently for each class.
Through polymorphism, the roles (interfaces) and implementations can be separated, allowing for flexible changes and extensions to the service implementation through overriding.
/** Polymorphism **/
class Employee {
constructor(name) { this.name = name; }
buy() { console.log(`${this.name} of ${this.constructor.name} class.`); }
}
class User {
constructor(name) { this.name = name; }
buy() { console.log(`${this.name} of ${this.constructor.name} class.`); }
}
const employee1 = new Employee("e1");
const employee2 = new Employee("e2");
const user1 = new User("u1");
const user2 = new User("u2");
const polymorphismArray = [employee1, employee2, user1, user2];
polymorphismArray.forEach((polymorphism) => polymorphism.buy());
// e1 of Employee
// e2 of Employee
// u1 of User
// u2 of User
Overriding vs. Overloading in Java
Overloading allows multiple methods with the same name but different parameters in a class, while overriding allows a subclass to provide its own implementation of a method that is already defined in its superclass.
// Overriding
class Animal {
public void sound() {
System.out.println("An animal makes a sound.");
}
}
class Dog extends Animal {
@Override
public void sound() { // subclass defines its own implementation
System.out.println("Woof!");
}
}
public class Main {
public static void main(String[] args) {
Animal animal = new Animal();
Dog dog = new Dog();
animal.sound(); // An animal makes a sound.
dog.sound(); // Woof!
}
}
// Overloading
class Calculator {
public int add(int x, int y) {
return x + y;
}
public int add(int x, int y, int z) {
return x + y + z;
}
} // two methods with the same method name, but with different parameters
public class Main {
public static void main(String[] args) {
Calculator calc = new Calculator();
int result1 = calc.add(5, 10); // 15
int result2 = calc.add(5, 10, 15); // 30
System.out.println(result1);
System.out.println(result2);
}
}
Dependency
Dependency refers to the degree to which an object (module or class) relies on another object during the collaboration process.
The term "dependency" implies that if one object is modified, other objects that depend on it may also be affected by the change.
If one object is changed, the other objects that depend on it will also be affected by the change.
Coupling
Coupling refers to the degree of dependency between objects and indicates how many dependencies an object has on other modules.
If the dependency between objects is too high, it is said to have a high degree of coupling.
When objects depend on each other at a reasonable level, it is said to have low coupling.
The higher the coupling between two objects, the higher the probability that they will change together, making it difficult to change.
Therefore, the goal of design should be to reduce the coupling between objects and create a design that is easy to change.
Cohesion
Cohesion refers to the degree of the relationship between the internal elements within a module.
An object that performs only closely related tasks and delegates unrelated tasks to other objects is said to have high cohesion.
The more a method uses variables internally, the higher the cohesion of the method and class.
Creating autonomous objects that handle their own data can not only reduce coupling but also increase cohesion.
To increase the cohesion of an object, it should take responsibility for its own data.
Object-Oriented Programming (OOP)
Programming Paradigm
Structured Programming
Object-Oriented Programming
Functional Programming
Why OOP?
If you copy and paste the same code to create an API, the code will be scattered in multiple places. When you need to modify the logic of the code, you will have to manually find and modify all the copied code, which can be a time-consuming task.
Efficient time management is important in programming. Therefore, it is essential to write clean code and minimize the amount of code that needs to be modified when changes occur. In order to achieve this goal, developers need to quickly detect and locate errors, fix them promptly, and minimize the time spent on development.
Object-oriented programming processes data and processes as a single unit, which allows developers to easily locate the code that caused the problem and modify only the relevant code instead of having to modify scattered code in multiple places. This makes it easy for developers to quickly understand the issue and resolve it efficiently.
SOLID
SOLID is used by programmers to create systems that are easy to maintain and extend over time.
SRP (Single Responsibility Principle)
The principle states that "an object should have only one responsibility". In other words, a class or module should have only one reason to change.
/** SRP Before **/
class UserSettings {
constructor(user) {
this.user = user;
}
changeSettings(userSettings) {
if (this.verifyCredentials()) {
//...
}
}
verifyCredentials() {
//...
}
}
// two different functionalities are defined in one single class
/** SRP After **/
class UserAuth {
constructor(user) {
this.user = user;
}
verifyCredentials() {
//...
}
}
class UserSettings {
constructor(user) {
this.userAuth = new UserAuth(user);
}
changeSettings(userSettings) {
if (this.userAuth.verifyCredentials()) {
//...
}
}
}
// separate into two different classes
OCP (Open-Closed Principle)
In software design, the principle of "Open-Closed" states that software entities (classes, modules, functions, etc.) should be open for extension but closed for modification. This means that the behavior of a software entity can be extended without modifying its source code. In other words, new features or components can be added to the software without affecting the existing code.
/** OCP Before **/
function calculator(nums, option) {
let result = 0;
for (const num of nums) {
if (option === "add") result += num;
else if (option === "sub") result -= num;
// when we want to add operations/functionalities, we need to change the code inside the function
}
return result;
}
console.log(calculator([2, 3, 5], "add")); // 10
console.log(calculator([5, 2, 1], "sub")); // -8
/** OCP After **/
function calculator(nums, callBackFunc) {
// change option to CallbackFunc
let result = 0;
for (const num of nums) {
result = callBackFunc(result, num); // no diverge (if statement) in option
}
return result;
}
// we now define functions outside of the calculator function
const add = (a, b) => a + b;
const sub = (a, b) => a - b;
const mul = (a, b) => a * b;
const div = (a, b) => a / b;
console.log(calculator([2, 3, 5], add));
console.log(calculator([5, 2, 1], sub));
LSP (Liskov Substitution Principle)
Program objects should be replaceable with instances of their subtypes without affecting the correctness of the program.
/** LSP Before **/
class Rectangle {
constructor(width = 0, height = 0) {
this.width = width;
this.height = height;
}
setWidth(width) {
this.width = width;
return this;
}
setHeight(height) {
this.height = height;
return this;
}
getArea() {
return this.width * this.height;
}
}
class Square extends Rectangle { // no need to be inherited from Rectangle for Square
setWidth(width) {
this.width = width;
this.height = width;
return this;
}
setHeight(height) {
this.width = height;
this.height = height;
return this;
}
}
const rectangleArea = new Rectangle() // 35
.setWidth(5) // 5
.setHeight(7) // 7
.getArea(); // 5 * 7 = 35
const squareArea = new Square() // 49
.setWidth(5) // 5
.setHeight(7) // 7
.getArea(); // 7 * 7 = 49
/** LSP After **/
class Shape { // this is just like a concept of interface
getArea() { //
}
}
class Rectangle extends Shape { // inherits Shape
constructor(width = 0, height = 0) { \
super();
this.width = width;
this.height = height;
}
getArea() {
return this.width * this.height; // define different method from Square
}
}
class Square extends Shape { // inherits Shape
constructor(length = 0) {
super();
this.length = length;
}
getArea() {
return this.length * this.length; // define different method from Rectangle
}
}
const rectangleArea = new Rectangle(7, 7) // 49
.getArea(); // 7 * 7 = 49
const squareArea = new Square(7) // 49
.getArea(); // 7 * 7 = 49
ISP (Interface Segregation Principle)
Having multiple interfaces for a specific client is better than having a single generic interface. It means that clients should not depend on interfaces that have unneeded functionality, and interfaces should be kept small as possible.
In simpler terms, it means that interfaces should be kept as minimal as possible to avoid clients from depending on features they don't actually need.
/** ISP Before - written in TypeScript **/
interface SmartPrinter {
print();
fax();
scan();
}
class AllInOnePrinter implements SmartPrinter {
print() {
// ...
}
fax() {
// ...
}
scan() {
// ...
}
}
class EconomicPrinter implements SmartPrinter {
print() {
// ...
}
fax() { // this is useless
throw new Error('fax not available.');
}
scan() { // this is useless
throw new Error('scan not available.');
}
}
/** ISP After **/
interface Printer {
print();
}
interface Fax { /
fax();
}
interface Scanner {
scan();
}
// separate functionalities into different interfaces
class AllInOnePrinter implements Printer, Fax, Scanner {
print() {
// ...
}
fax() {
// ...
}
scan() {
// ...
}
}
class EconomicPrinter implements Printer {
print() { // more efficient
// ...
}
}
class FacsimilePrinter implements Printer, Fax {
print() {
// ...
}
fax() {
// ...
}
}
DIP (Dependency Inversion Principle)
Programmers should depend on abstractions, not on concrete implementations. This means that high-level modules (domains) should not depend on low-level modules (substructures).
/** DIP Before **/
const readFile = require('fs').readFile;
class XmlFormatter {
parseXml(content) {
// convert Xml to string
}
}
class JsonFormatter {
parseJson(content) {
// convert JSON to string
}
}
class ReportReader {
async read(path) {
const fileExtension = path.split('.').pop(); // file extension
if (fileExtension === 'xml') {
const formatter = new XmlFormatter();
const text = await readFile(path, (err, data) => data);
return formatter.parseXml(text);
} else if (fileExtension === 'json') { // if statement inside
const formatter = new JsonFormatter();
const text = await readFile(path, (err, data) => data);
return formatter.parseJson(text);
}
}
}
const reader = new ReportReader();
const report = await reader.read('report.xml');
// or
// const report = await reader.read('report.json');
/** DIP After **/
const readFile = require('fs').readFile;
class Formatter { // just an interface for JS
parse() { }
}
class XmlFormatter extends Formatter {
parse(content) {
// convert Xml to string
}
}
class JsonFormatter extends Formatter {
parse(content) {
// convert JSON to string
}
}
class ReportReader {
constructor(formatter) { // get XmlFormatter and JsonFormatter that inherit Formatter interface
this.formatter = formatter;
}
async read(path) {
const text = await readFile(path, (err, data) => data);
return this.formatter.parse(text); // data parsing using the abstracted formatter
}
}
const reader = new ReportReader(new XmlFormatter());
const report = await reader.read('report.xml');
// or
// const reader = new ReportReader(new JsonFormatter());
// const report = await reader.read('report.json');
More Resource
![[코테] 그리디 문제 - 무지의 먹방 라이브](https://cdn.hashnode.com/res/hashnode/image/upload/v1712215455263/1ac1f35a-8862-4e42-8d0c-e2bea01e04c0.png)
![[코테] Bfs 토마토](https://cdn.hashnode.com/res/hashnode/image/upload/v1709032619170/70056896-c857-444b-9c99-45bfcb466806.png)
![[코테] Dfs 문제 유형 - 그래프 내에서 구분하여 카운트 하기](https://cdn.hashnode.com/res/hashnode/image/upload/v1709019361383/b0585d72-c808-4169-83a9-2724f312e927.png)
![[코테] DFS vs BFS](https://cdn.hashnode.com/res/hashnode/image/upload/v1708971211123/71f9386c-6a62-43b2-a602-4d084c24d6cf.png)
![[코테] 여행경로](https://cdn.hashnode.com/res/hashnode/image/upload/v1708971251412/27ce72ed-8ee7-4d13-a02f-ff4bbe50c4be.png)