자료 구조
자료를 그대로 공개하며 별다른 함수 제공 X
class Point {
public:
double x;
double y;
};
내부 구조가 노출되어,
직접적으로 좌표값을 읽고 설정할 수 있다.
직접적인 접근을 제한하기 위해
비공개 private로 선언하고
getter와 setter 함수를 제공한다
class Point {
private:
double x, y;
public:
double getX() {return x;}
double getY() {return y;}
}
함수라는 계층을 넣었다고 완전히 감춰지지 않는다.
내부 구조를 노출하는 구조에 해당된다.
객체
추상화 뒤로 자료를 숨긴 채 자료를 다루는 함수만 공개
class Point {
public:
virtual double getX() = 0;
virtual double getY() = 0;
virtual void setCartesian(double x, double y) = 0;
virtual double getR() = 0;
virtual double getTheta() = 0;
virtual void setPolar(double r, double theta) = 0;
};
어떤 좌표계를 사용하는지 내부를 정확히 알 수 없다
ex) (X, Y)를 사용하는지 (r, θ) 를 사용하는지
실제 구현을 모른 채 추상 클래스를 제공받아
자료를 조작할 수 있어야 진정한 의미의 클래스
• 자료 구조: 자료를 그대로 공개하며 별다른 함수는 제공 X
• 객체: 추상화 뒤로 자료를 숨긴 채 자료를 다루는 함수만 공개
* 두 개념은 사실상 정반대
자료구조 기반 절차적인 코드
struct Shape {
public:
virtual ~Shape() { }
};
struct Point : public Shape {
};
struct Square : public Shape {
Point topLeft;
double side;
};
struct Rectangle : public Shape {
Point topLeft;
double height;
double width;
};
struct Circle : public Shape {
Point center;
double radius;
};
#define PI 3.141592653589793
class Geometry {
public:
double area(Shape* pShape) {
if (nullptr != dynamic_cast <Square*>(pShape)) {
Square& s = *(Square*)pShape;
return s.side * s.side;
}
else if (nullptr != dynamic_cast <Rectangle*>(pShape)) {
Rectangle& r = *(Rectangle*)pShape;
return r.height * r.width;
}
else if (nullptr != dynamic_cast <Circle*>(pShape)){
Circle& c = *(Circle*)pShape;
return PI * c.radius * c.radius;
}
}
};
도형(Square, Rectangle, Circle) 자료구조에
넓이(area) 구하는 함수를 직접 구현해주지 않고
Geometry 클래스르 통해서 각 도형의 넓이를 구해주었다.
Q) Geometry 클래스에 둘레 구하는 함수 perimeter() 추가한다면?
#include <iostream>
using namespace std;
struct Shape {
public:
virtual ~Shape() {}
};
struct Point : public Shape {
};
struct Square : public Shape {
Point topLeft;
double side;
};
struct Rectangle : public Shape {
Point topLeft;
double height;
double width;
};
struct Circle : public Shape {
Point center;
double radius;
};
#define PI 3.141592653589793
class Geometry {
public:
double area(Shape* pShape) {
if (nullptr != dynamic_cast<Square*>(pShape)) {
Square& s = *(Square*)pShape;
return s.side * s.side;
}
else if (nullptr != dynamic_cast<Rectangle*>(pShape)) {
Rectangle& r = *(Rectangle*)pShape;
return r.height * r.width;
}
else if (nullptr != dynamic_cast<Circle*>(pShape)) {
Circle& c = *(Circle*)pShape;
return PI * c.radius * c.radius;
}
}
double perimeter(Shape* pShape) {
if (nullptr != dynamic_cast<Square*>(pShape)) {
Square& s = *(Square*)pShape;
return (s.side + s.side) * 2;
}
else if (nullptr != dynamic_cast<Rectangle*>(pShape)) {
Rectangle& r = *(Rectangle*)pShape;
return (r.height + r.width) * 2;
}
else if (nullptr != dynamic_cast<Circle*>(pShape)) {
Circle& c = *(Circle*)pShape;
return 2 * PI * c.radius;
}
}
};
장점
도형 클래스(Square, Rectangle, Circle)는 아무 영향도 받지 않는다 !
(도형 클래스에 의존하는 다른 클래스도 마찬가지)
새로운 함수가 필요하다면 Geometry에만 추가하면 된다.
단점
새로운 도형을 추가한다면
Geometry 클래스에서
해당 도형을 처리하는 모든 함수를 점검해야 한다.
객체 지향 기법
struct Shape {
public:
virtual ~Shape() { }
virtual double area() const = 0;
};
struct Point : public Shape {
public:
double area() const { return 1;}
};
#define PI 3.141592653589793
struct Square : public Shape {
private:
Point topLeft;
double side;
public:
double area() const {
return side * side;
}
};
struct Rectangle : public Shape {
private:
Point topLeft;
double height;
double width;
public:
double area() const {
return height * width;
}
};
struct Circle : public Shape {
private:
Point center;
double radius;
public:
double area() const {
PI* radius* radius;
}
};
class Geometry {
public:
double area(const Shape& shape) {
return shape.area();
}
};
각 도형 클래스마다 넓이 구하는
area() 함수가 구현되어 있다.
Q) Geometry 클래스에 둘레 구하는 함수 perimeter() 추가한다면?
#include <iostream>
using namespace std;
class Shape {
public:
virtual ~Shape() {}
virtual double area() const = 0;
virtual double perimeter() const = 0; // 추가
};
class Point : public Shape {
public:
double area() const { return 1;}
virtual double perimeter() const { return 1; } // 추가
};
#define PI 3.141592653589793
class Square : public Shape {
private:
Point topLeft;
double side;
public:
double area() const {
return side * side;
}
virtual double perimeter() const { // 추가
return (side + side) * 2;
}
};
class Rectangle : public Shape {
private:
Point topLeft;
double height;
double width;
public:
double area() const {
return height * width;
}
double perimeter() const { // 추가
return (height + width) * 2;
}
};
struct Circle : public Shape {
private:
Point center;
double radius;
public:
double area() const {
return PI * radius * radius;
}
double perimeter() const { // 추가
return 2 * PI * radius;
}
};
class Geometry {
public:
double area(const Shape& shape) {
return shape.area();
}
double perimeter(const Shape& shape) { // 추가
return shape.perimeter();
}
};
장점
새로운 도형 클래스를 추가하기 쉽다.
ex) Shape를 구현하는 도형을 만들면 된다.
단점
둘레구하기 등 새로우 함수 추가시
각 도형 클래스마다 구현해야 하기 때문에 어렵다.
요약
• 절차지향: 기존 자료 구조를 변경하지 않으면서 새로운 함수를 추가하기 쉽다.
ex) 새로운 도형 추가시 다루는 함수 점검 필요
• 객체지향: 기존 함수를 변경하지 않으면서 새로운 클래스 추가하기 쉽다.
ex) 기존 함수를 만족하는 클래스를 만들면 된다.
* 두 구조 차이를 알아보기 위해 새로운 함수를 추가하였지만
새로운 도형을 추가해서 차이를 느낄 수도 있다.
Q) 그럼 우리는 무엇을 선택해야 할까?
A) 개발자는 직면한 문제에 최적인 해결책을 선택 해야한다
<자료구조 기반 절차 지향>
별다른 동작 없이 자료를 노출한다.
그래서 기존 자료 구조에 새 동작을 추가하기는 쉽다.
하지만 기존 함수에 새로운 자료 구조를 추가하기 어렵다
→ 새로운 동작(함수)가 요구되는 경우
<객체 지향>
동작을 공개하고 자료를 숨긴다.
그래서 기존 동작을 변경하지 않으면서 새 객체 타입을 추가하기 쉽다
하지만 기존 객체에 새로운 동작을 추가하기는 어렵다
→ 새로운 자료 타입이 요구되는 경우
'까망 동네 > 클린 코드' 카테고리의 다른 글
[클린코드] 형식 (Format) (0) | 2022.07.18 |
---|---|
[클린코드] 함수 Function (0) | 2022.07.18 |
[클린코드] 주석 Comment (0) | 2022.07.18 |
[클린코드] 의미 있는 이름 (0) | 2022.07.17 |
클린 코드(Clean Code)란? (0) | 2022.07.17 |
댓글