Notation ' : 강세 단어 ↗: 억양 올림 ↘: 억양 내림 //: 호흡을 두는 부분
지문 1: 광고문
'Welcome to GreenFit 'Gym.↘// We are 'offering a free one-week 'tiral for all new 'members.↘//'Join today↗ and enjoy our state-of-the-art e'quipment↗//'daily 'yoga classes↗ and 'friendly 'trainers.↘//'Visit the front 'desk for more infor'mation.↘
지문 2: 공지문
At'tention 'passengers,↘ // The 5:30 'train to 'Boston is de'layed due to 'weather con'ditions.↘// It is now 'scheduled to 'leave at 6:15↗ from 'platform three.↘// We a'pologize for the incon'venience.↘
지문 3: 호텔 안내문
'Welcom to the Grandview 'Hotel.↘//'Breakfast is 'served in the main 'dining room↗ on the first 'floor↗ from 6:30 to 10 a.m. every'day.↘'Guests are also 'welcome to use the 'fitness center↗ and 'swimming pool,↗ which are lo'cated next to the 'lobby.↘// If you 'need any as'sitance,↗ please 'contact the front 'desk at any 'time.↘// We 'hope you en'joy your 'stay with us.↘
지문 4: 회사 공지
At'tention, em'ployees:↘// Please re'member that the com'pany's annual 'health and 'safety 'training↗// will take 'place this 'Friday↗// in 'Conferency Room B.↘// All 'staff members are re'quired to at'tend the 'session↗// which will be'gin at 2 p.m.↗// and 'last approxi'mately 1 'hour.↘ If you have a 'scheduling con'flict,↗// please in'form your 'manager in ad'vance.↘//'Thank you for your co'operation.↘
Notation ' : 강세 단어 ↗: 억양 올림 ↘: 억양 내림 //: 호흡을 두는 부분
지문 1
'Welcom to our 'Customer 'Service 'Center.↘ We are 'open 'Monday throug 'Friday↗'from 9 a.m. to six p.m.↘ If you 'need any as'sisance,↗ please do not 'hesitate to 'ask one of our 'staff 'members.
지문 2
'Thank you for 'calling Green 'Valley 'Library.↘ Our 'library is 'open from 10 a.m. to 8 p.m. on 'weekdays↗and from 10 a.m. to 5 p.m. on 'Saturdays.↘ We are 'closed on 'Sundays and 'national 'holidays.↘ For more informa'tion about our 'programs and e'vents,↗ please 'visit our 'website or 'ask one of our 'staff 'members.
지문 3
'Good after'noon↗// and 'welcome to 'City Mu'seum.↘ Our 'special exhi'bition,↗// "'Art of the '20th 'Century,"↗// will be on dis'play until the 'end of this 'month.↘'Visitors can en'joy↗//'paintings, 'sculptures,↗// and 'photographs from 'artists a'round the 'world.↘'Guided 'tours are a'vailable 'twice a 'day,↗// at '11 a.m. and '3 p.m.,↗// and 'audio 'guides can be 'rented at the 'front desk.↘ We 'hope you have a 'wonderful 'time↗// ex'ploring the exhi'bition.
-. Factory Pattern: 객체의 생성 동작을 별도의 Class로 분리해 처리하거나 별도의 Method를 호출해 처리
-. Singleton Pattern: 선언된 Class로 복수 객체를 생성할 수 없도록 제한하며, 제한된 단일 객체는 공유와 충돌 방지
-. Factory Method Pattern: Factory Pattern에 추상화를 결합해 객체의 생성과 사용을 분리하는 방식으로, 선언된 Class의 객체를 직접 Code로 생성하지 않고 별도로 준비한 추상 Method에 생성 위임
-. Abstract Factory Pattern: Factory Method Pattern 보다 좀 더 큰 그룹 단위 객체를 생성/관리하는 방식으로 Factory에 Interface를 활용해 객체를 생성함으로써 Factory를 Factory의 군(Family)으로 변경
-. Builder Pattern: 추상 Factory Pattern을 확장한 Pattern으로 복잡한 구조의 복합 객체를 생성하기 위한 단계를 정의하고 각 단계 별 수행 동작을 변경하면서 Builder Pattern으로 생성
-. Prototype Pattern: 새로운 객체를 생성하지 않고 기존의 객체를 복제하는 방식으로 자원을 절약하는 Pattern
1. Class
: 객체지향 프로그래밍(OOP, Object Oriented Programming)의 핵심 개념으로, Data와 Data를 조작하는 함수를 하나의 논리적 단위로 묶는 사용자 정의 Data Type
1.1 주요 구성요소
-. Member Variant(멤버 변수): Class 내에서 Data를 저장하기 위한 변수로 객체의 속성을 나타냄
-. Member Function(멤버 함수): Class 내의 Data(Member Variant)를 조작하거나 동작을 정의하는 함수
-. Access Specifier(접근 제한자): Class Member의 접근 권한 제어
-. public: 외부에서 접근 가능
-. private: Class 내부에서만 접근 가능
-. protected: 파생 Class에서 접근 가능
-. Constructor(생성자): 객체 생성 시 자동으로 호출되는 함수로 Member Variant를 자동으로 초기화 하거나 객체 초기 설정에 사용
-. Destructor(소멸자): 객체가 소멸할 때 자동으로 호출되는 함수로 자원 해제 등에 사용
1.2 Class의 특징
-. Inheritance(상속): 기존 Class(부모 Class)의 속성과 가능을 다른 Class(자식 Class)가 물려받아 Code의 재사용성을 높임
-. Polymorphism(다형성): 같은 이름의 함수가 다른 동작을 수행할 수 있음
e.g., 함수 Over loading, 가상함수, ...
-. Encapsulation(캡슐화): Data와 함수를 하나로 묶고 외부에서 직접 접근 제한
-. Abstraction(추상화): 실제 Code를 개발하기 전에 구체적인 내용은 배제하고 개략적인 정보만 선언하는 것으로 추상적 개념과 실제 Code를 분리하는 효과가 있어 향후 개발 중인 Code를 예정하고 선언만으로 주변 가능을 작성할 수 있다는 장점이 있음
객체가 생성될 때 멤버 변수를 초기화하는 방법으로, 생성자 내부에서도 멤버 변수를 초기화할 수 있으나, 생성자 초기화 리스트가 더 효율적
Car(Engine* e){
engine = e; // 생성자 내부에서 객체 초기화
}
2) Interface 사용
: Class 간 결합도를 줄이기 위해 Interface를 사용해 의존성을 느슨하게 만듦
-> C++에서는 Interface를 직접적으로 지원하지 않지만 순수가상함수(Pure Virtual Function)를 가진 Class를 통해 Interface 구현
#include <iostream>
using namespace std;
// Interface 정의
class IEngine{
public:
virtual void start() = 0; // 순수가상함수(Interface)
virtual ~IEngine(){} // 가상 소멸자
{;
// Engine Class: IEngine Interface 구현
class Engine: public IEngine{
public:
void start() override{
cout << "Standard Engine started" << endl;
}
};
// Electric Engine Class: IEngine Interface 구현
class Electric Engine: public IEngine{
public:
void start() override{
cout << "Electric Engine started silently" << endl;
}
};
// Car Class
class Car{
private:
IEngine* engine; // IEngine Interface 참조
public:
Car(IEngine* e): engine(e){} // 의존성 주입
void startCar(){
engine->start(); // IEngine의 start() 호출
cout << "Car is ready to drive" << endl;
}
};
int main(){
Engine standardEngine; // Standard Engine 객체 생성
ElectricEngine electricEngine; // Electric Engine 객체 생성
Car car1(&standardEngine); // Standard Engine 주입
car1.startCar();
Car car2(&electricEngine); // Electric Engine 주입
car2.startCar();
return 0;
}
Output
Standard Engine started
Car is ready to drive
Electric Engine started
Car is ready to drive
3) 의존성 역전 원칙(DIP, Dependency Inversion Principle)
: 고수준 Module은 저수준 Module에 의존하지 않고 모두 추상화에 의존해야 함
4) DI Framework 사용
: Google Guide와 같은 DI Framework 사용
3.3 복합 객체
하나의 객체가 다른 객체를 포함하거나 참조하는 구조로, 종속적이고 연관 관계를 가짐
1) 포함관계 (Composition)
: 강한 관계로 객체가 다른 객체를 직접 포함하며, 포함된 객체는 전체 객체와 생명주기 공유
#include <iostram>
using namespace std;
// Korean Class
class Korean{
public:
string text() const { return "안녕하세요"; }
};
// Hello Class (포함관계)
class Hello{
private:
Korean korean; // Korean 객체 직접 포함 (포함관계)
public:
Hello(){}
// greeting Method
string greeting() const { return korean.text(); }
};
int main(){
Hello hello; // Hello 객체 생성 (Hello 객체 생성되면서 Korean 객체도 함께 생성됨)
cout << hello.greeting() << endl;
return 0;
}
Output
안녕하세요
2) 집합 관계 (Aggregation)
: 느슨한 관계로, 객체가 다른 객체를 참조하거나 포인터로 소유하며 포함된 객체는 독집적으로 생성 및 소멸되어 생명주기를 공유하지 않음
#include <iostream>
#include <memory>
using namespace std;
class Korean{
public:
string text() const { return "안녕하세요"; }
}
class Hello{
private:
shared_ptr<Korean> korean; // Korean 객체 참조 (집합 관계)
public:
Hello(shared_ptr<Korean> obj): korean(obj) {} // 외부에서 Korean 객체 전달 받음
string greeting() const { return korean -> text(); )
};
int main(){
shared_ptr<Korean> koreanObj = make_shared<Korean>(); // Korean 객체를 독립적으로 생성
Hello hello(koreanObj); // Korean 객체를 Hello 객체에 전달(생명주기를 공유하지 않음)
cout << hello.greeting() << endl;
return 0;
}
Output
안녕하세요
구분
포함 관계
집합 관계
객체 간 관계
한 객체가 다른 객체를 완전하게 소유
한 객체가 다른 객체를 참조만 함
생명주기
포함된 객체는 포함하는 객체가 소멸될 때 함께 소멸
포함된 객체는 독립적으로 존재 가능
설계 방법
포함된 객체를 Class의 멤버변수로 직접 선언
포함된 객체를 포인터 또는 스마트포인터로 참조
3.3.2 복합객체의 장단점
1) 장점
-. 모듈화: 전체와 부분으로 나누어 설계하면 Code의 재사용성과 유지보수성 증가
-. 확장성: 객체를 구성하는 부분 객체를 교체하거나 확장하기 용이
-. 캡슐화: 복합 객체 내부의 세부 사항을 숨기고 외부에는 필요한 동작만 제공
2) 단점
-. 의존성 증가: 복합객체와 구성요소 간의 강한 의존성으로 인해 변경이 전체에 영향을 줄 수 있음
-. 복잡성 증가: 객체 간의 관계를 설계하고 관리하는 데 추가적인 노력 필요
4 생성 패턴(Creational Pattern)
4.1 new keyword
: 선언된 Class를 기반으로 객체를 생성하는 Instance화를 수행하며 객체 간에 강력한 의존 관계를 갖는 구조적 문제 발생
4.2 생성 패턴(Creational Pattern)
: 객체 생성을 위임해 별개의 Class로 분리하고 Program 내에서 객체 생성이 필요한 경우 분리 설계된 Factory 객체에 생성을 위임함으로써 느슨한 결합 관계로 변경
* 공장(Factory): Design Pattern에서 객체를 생성하고 캡슐화해 위임하는 것
5 Factory Pattern
: 객체 생성 로직을 캡슐화해 Client Code가 직접 객체의 생성 방법을 알 필요 없도록 하는 Design Pattern
즉, 객체를 생성하는 공장을 만드는 Pattern으로 객체 생성을 위한 Interface를 제공하면서 실제 생성 작업은 Sub-Class나 Factory Class에서 수행해 느슨한 결합 관계로 변경
-. 장점: 개발 과정에서 Class의 이름이 변경되어도 Code를 일일히 수정하지 않고 Factory 객체를 통해 쉽게 변경 가능(유연성과 확장성 개선)
-. 단점: 객체 생성을 위임하는 데 별도의 Class가 필요
-> 단순 팩토리로 이 단점 보완 가능
5.1 느슨한 결합
: 객체 생성은 객체 간 결합 관계를 발생시키고, 객체 간에 의존성 부여
-> Factory Pattern에서는 Code에서 직접 Class의 이름을 지정해 객체를 생성하지 않으며, 별도의 객체에 필요한 객체를 생성하도록 책임을 위임하는 과정에서 중복 코드 정리
#include <iostream>
#include <memory>
using namespace std;
// 1 제품 Class 정의 (Korean & English)
class Language{
public:
// 객체의 불필요한 변경을 방지하고, Code의 안정성과 최적화를 향상시키기 위해 상수(const) 사용
virtual string text() const = 0; // 순수가상함수
virtual ~Language(){} // 가상소멸자
};
class Korean: public Language{
public:
string text() const override { return "안녕하세요"; }
};
class English: public Language{
public:
string text() const override { return "Hello"; }
};
// 2 Factory Class 정의
class Factory{
public:
static unique_ptr<Language> getInstance(const string& type){
cout << "Factory: 객체를 생성합니다" << endl;
if(type == "ko") return make_unique<Korean>(); // 스마트포인터를 사용해 객체 반환
else if(type == "en") return make_unique<English>();
return nullptr; // 잘못된 type이면 nullptr 반환
}
};
5.3 단순 팩토리(Simple Factory) Pattern = 정적 팩토리(Static Factory) Pattern
: Factory Pattern의 특징과 처리 로직을 간략하게 작성한 것으로 별개의 Factory Pattern 객체를 생성하는 것이 아닌 자신의 객체에 필요한 객체를 생성하는 전용 Method 추가
즉, 객체 생성 로직이 단일함수에 캡슐화
#include <iostream>
#include <memory>
using namespace std;
// Korean Class 정의
class Korean{
public:
string text() const{ return "안녕하세요"; }
};
// Hello Class 정의
class Hello{
public:
// greeting Method
string greeting() const{
// Factory Method 호출해 Korean 객체 생성
unique_ptr<Korean> ko = factory();
return ko->text();
}
// Factory Method (정적 Method)
static snique_ptr<Korean> factory(){
return make_unique<Korean>() // C++에서는 동적 객체를 반환할 때 스마트 포인터를 사용하는
// 것이 메모리 관리 측면에서 안전
}
};
// main 함수
int main(){
Hello hello;
cout << hello.greeting() << endl;
return 0;
}
Output
안녕하세요
-. 장점: 객체 생성 과정이 복잡하지 않은 경우 추가 Class 파일을 생성하지 않고도 Factory Pattern 적용 가능
6 정리
Factory Pattern : 객체 생성 시 확장과 수정을 쉽게 하기 위한 설계 방법으로 객체 생성 과정을 분리해 처리하기 때문에 객체 생성 과정에서 발생하는 new keyword의 문제점을 해결하고 느슨한 객체 생성 관리 다양한 Class의 객체 생성을 쉽게 처리하며 생성하는 객체를 정의할 수 없거나 변경이 있는 경우 객체 생성을 매우 유용하게 관리 가능 -> Factory Pattern이 Proxy Pattern과 결합하면 객체 생성을 위임받을 때 권한에 따라 접근하는 것을 제어할 수 있으며, 특정 객체 생성에서 보안 또는 권한 등의 처리가 필요할 때 응용 가능 [ Problem ] 객체지향개발에서 객체 간 의존성은 객체를 생성할 때마다 발생하며 Code에서 직접 생성한 객체는 의존성이 강력해 유지보수와 수정이 어려움 [ Solve ] 의존 관계가 강력한 결합 관계를 느슨한 결합 관계로 변경 [ 장점 ] 객체 생성을 다른 객체에 위임함으로써 내부적 결합을 제거하고 동적으로 객체 관리 가능 객체 생성 처리를 동적으로 위임하므로 향후 Class가 추가되거나 변경돼도 Code를 쉽게 수정 가능 [ 단점 ] Factory Pattern을 사용하면 객체를 직접 생성하는 것과 달리 Factory 객체를 통해 호출하는 처리 과정이 한 단계 더 필요하므로, 이는 불필요한 호출 증가로 Program 성능 저하를 초래할 수 있음
Simple Factory Pattern (단순 팩토리 패턴) : Method를 통해 객체 생성을 관리해 가장 간단하고 깔끔하게 Class의 객체를 생성하는 의존적인 연관관계 해소 가능
같은 Interface(기본 Class)를 통해 다양한 구현을 사용하며, 기본 Class에 대한 의존성을 유지하면서도 구현을 독립적으로 관리 가능
3.1 SRP(Single Responsibility Principle, 단일 책임의 원칙)
: 하나의 클래스는 하나의 기능(책임)만 담당하며, 변경 이유도 하나여야 함
ERROR CASE : 하나의 Class가 계산, 출력, 저장 등 여러 책임을 가짐
class Invoice{
public:
void calculateTotal(){
// 계산 로직
}
void printInvoice(){
// 출력 로직
}
void saveToDatabase(){
// 저장 로직
}
};
GOOD CASE
: 각각의 책임을 별도의 Class로 분리
class InvoiceCalculator{
public:
void calculateTotal(){
// 계산 로직
}
};
class InvoicePrinter{
public:
void printInvoice(){
// 출력 로직
}
};
class InvoiceRepository{
public:
void saveToDatabase(){
// 저장 로직
}
};
3.2 OCP(Open/Close Principle, 개방 폐쇄 원칙)
: 코드는 확장에 열려 있고 수정에는 닫혀있어야 하며, 새로운 기능을 추가할 때 기존 코드를 수정하지 않고 확장할 수 있도록 설계
ERROR CASE
: 도형의 종류가 늘어날 때 마다 calculateArea method 수정 필요
class Shape{
public:
std::string type;
};
class AreaCalculator{
public:
double calculateArea(const Shape& shape){
if(shape.type == "circle"){
// 원의 면적 계산
} else if(shape.type == "rectangle"){
// 사각형의 면적 계산
}
return 0;
}
};
: 해당 함수가 가상함수(Virtual Function)임을 의미하며, 기본 Class(Base Class)에서 선언된 이 함수는 파생 Class(Derived Class)에서 재정의 할 수 있음.
동일한 기본 Class를 상속 받더라도 파생 Class 마다 가상함수의 구현부를 다르게 정의할 수 있으며, 이를 통해 다형성 실현
-. virtual double calculateArea() const = 0;
: 함수의 구현부가 없음을 명시하며, 이렇게 선언된 함수는 순수 가상함수(Pure Virtual Function)로 선언되어 반드시 파생 Class에서 구현되어야 함
-. 순수 가상함수(Pure Virtual Function)
: 기본 Class에서는 Interface만 제공하고 실제 구현은 파생 Class에서 정의하도록 강제함.
이 경우 기본 Class는 추상 Class(Abstract Class)로 변환되며, 추상 Class는 Interface화 할 수 없음
-. virtual ~Shape() = default;
: 소멸자(Destructor)로 객체가 삭제되거나 수명이 끝날 때 호출되는 Class의 특별한 member function으로, Class의 자원을 정리하거나 Memory 해제를 위해 사용
소멸자는 함수의 일정이므로 함수 정의처럼 이름 뒤에 ( )를 사용하고, 매개변수를 가질 수 없으므로 항상 빈 괄호로 선언
-. override
: 해당 함수가 순수 가상함수인지 여부와 상관 없이 부모 Class에서 선언된 가상함수를 재정의한다는 것을 명시적으로 나타냄
부모 Class에서 virtual로 선언된 함수만 override keyword로 재정의 가능하며, override keyword를 사용하지 않아도 재정의는 가능하나, override를 사용하면 Compiler가 재정의 여부를 확인하고 함수명이나 Signature가 다르면 Compile Error 발생
-. 부모 Class에서 virtual로 선언된 함수: 자식 Class에서 선택적으로 재정의 가능하며, 재정의하지 않는 경우 부모 Class의 구현 그대로 사용
-. 부모 Class에서 순수가상함수로 선언된 함수: 자식 Class에서 반드시 선언 및 구현이 필요하며 자식 Class가 이를 구현하지 않으면 자식 Class도 추상 Class가 되며 Instance 화 불가능
class Work{
public:
virtual void work1(){ // 기본 구현 제공
std::cout << "Base work!" << std::endl;
}
virtual void work2() = 0;
};
class Human: public Work{
// work1()을 선언 및 구현하지 않아도 Error 없음
// work2()는 선언해주지 않으면 compile error 발생
void work2() override{
std::cout << "I am working!" << std::endl;
}
};
int main(){
Human h;
h.work1(); // 부모 Class의 work() 호출: "Base work!"
h.work2(); // "I am working!"
return 0;
}
: 자식 Class는 부모 Class의 역할을 대체할 수 있어야 하며, 부모 Class의 동작을 깨뜨리거나 위반하지 않는 설계 필요
ERROR CASE
: Penguin Class가 Bird의 역할을 대체하지 못함
class Bird{
public:
virtual void fly(){
std::cout << "I can fly!" << std::endl;
}
};
class Penguin: public Bird{
public:
void fly() override{
throw std::logic_error("Penguin can't fly!");
}
};
GOOD CASE
: Bird와 FlyingBird로 역할 분리
class Bird{
public:
virtual void eat(){
std::cout << "I can eat!" << std::endl;
}
virtual ~Bird() = default; // 소멸자
};
class FlyingBird: public Bird{
public:
virtual void fly(){
std::cout << "I can fly!" << std::endl;
}
};
class Penguin: public Bird(){
public:
void eat() override{
std::cout << "I am a penguin and I eat fish!" << std::endl;
}
};
class Sparrow: public FlyingBird{
public:
void fly() override{
std::cout << "I am a sparrow and I can fly!" << std::endl;
}
};
객체가 삭제되거나 수명이 끝날 때 호출되는 Class의 특별한 member 함수로, Class의 자원을 정리하거나 memory 해제를 위해 사용
소멸자는 함수의 일종이므로 함수 정의처럼 이름 뒤에 ( )를 사용하고 매개변수를 가질 수 없으므로 항상 빈 괄호로 선언
가상 소멸자(Virtual Destructor)
: Class가 상속될 때 자식 Class에서 자원을 적절히 해제하도록 설계된 소멸자로, 부모 Class의 소멸자를 virtual로 선언하면 부모 Class를 통해 파생 Class 객체를 삭제할 때 파생 Class의 소멸자도 호출
소멸자를 virtual로 선언하면 상속 받는 Class에서 동적 할당된 객체가 안전하게 삭제되며, 부모 Class의 소멸자가 가상함수가 아니면 부모 Class의 pointer로 파생 Class 객체를 삭제할 때 부모 Class의 소멸자만 호출하고 파생 Class의 소멸자는 호출되지 않아 memory 누수 발생 가능
→ default를 사용해 compiler에게 기본 소멸자를 생성하도록 지시해 개발자가 특별히 소멸자의 구현을 작성하지 않아도 기본 동작(자원 해제)을 자동으로 수행
3.4 ISP(Interface Segregation Principle, 인터페이스 분리의 원칙)
: Client는 자신이 사용하지 않는 Method에 의존하지 않아야 하며, 큰 Interface를 작고 구체적인 Interface로 분리
ERROR CASE
: 로봇은 eat() Method가 필요하지 않지만 Worker Interface 때문에 구현해야 함
class Worker{
public:
virtual void work() = 0;
virtual void eat() = 0;
};
class Robot: public Worker{
public:
void work() override{
std::cout << "I am working!" << std::endl;
}
void eat() override{
// 로봇은 eat를 사용할 필요가 없음
}
};
GOOD CASE
: Interface를 분리해 필요하지 않은 의존성 제거
class Workable{
public:
virtual void work() = 0;
virtual ~Workable() = default;
}
class Eatable{
public:
virtual eat() = 0;
virtual ~Eatable() = default;
};
class Human: public Workable, public Eatable{
public:
void work() override{
std::cout << "I am working!" << std::endl;
}
void eat() override{
std::cout << "I am eating!" << std::endl;
}
};
class Robot: public Workable{
public:
void work() override{
std::cout << "I am working!" << std::endl;
}
};
: C++11 부터 제공되는 Smart Pointer(std::shared_ptr, std::unique_ptr)를 사용하면 객체의 Life Cycle을 안전하게 관리 가능
4 GoF(Gang of Four)
: Erich Gamma, Richard Helm, Ralph Johnson, John Vissides의 저서인 'GoF의 디자인패턴'을 가리키는 용어로, 처음 소프트웨어 공학에서 사용되는 패턴을 정리한 사람들의 별칭
객체지향의 문제점을 분석해 24개의 패턴으로 분류
5 패턴의 요소
: 24개로 분리된 디자인 패턴은 공통된 4가지 요소를 가짐
5.1 이름(Pattern Name)
: 패턴 이름을 통해 피턴의 용도를 직관적으로 이해 가능
5.2 문제(Problem)
: 각 패턴은 해결하고자 하는 문제를 갖고 있으며, 이러한 문제들은 패턴 적용을 고려해야 하는 시점을 암시
5.3 해법(Solution)
: 문제점을 인식했다면 객체 요소 간 관계를 정리하고 객체들을 추상화 및 나열함으로써 해결 방법을 찾음
5.4 결과(Consequence)
: 디자인 패턴은 알려진 문제점들을 해결하는 데 효과적이지만 모든 코드에서 디자인 패턴이 유용하다고 볼 수는 없으므로 꼭 필요한 경우를 생각해 적절히 분배해 사용해야 함
6 디자인 패턴의 사용 목적
6.1 통일성
: 디자인 패턴은 개발 방법을 정의함으로써 보다 통일화된 코드를 작성할 수 있음
6.2 실체화(Reification)
: 구현(Implementation) 보다는 디자인을 의미하며, 구조만으로 패턴을 파악하는 것은 불가능하므로 의도를 파악하는 것이 중요
6.3 유지보수 및 방어적 설계
: 향후 추가되는 코드를 수정하기 쉽게 변경할 수 있도록 구현해야 하며, 오랫동안 문제 없이 유지보수를 하기 위해 변경 가능한 디자인(Design for change)으로 설계해야함
코드를 작성하면서 어떤 부분이 향후 수정될 것으로 예측된다면 해당 기능을 방어적으로 처리할 수 있도록 코드가 설계되어야 하며, 지속적인 리팩토링 작업을 수행해 주어야 함
디자인 패턴 vs 처리 성능 성능 최적화를 위해서는 많은 함수 호출과 객체 간 호출이 적을 수록 좋으나 디자인 패턴에서는 코드의 가독성과 유지보수를 위해 객체의 메소드를 분리하며 호출도 자주 발생하게 되므로 패턴을 너무 많이 사용하는 경우 잦은 메소드 호출로 인해 성능이 저하될 수 있음
티어 : Silver 1 시간 제한 : 1 초 메모리 제한 : 128 MB 알고리즘 분류 : 그래프 이론, 그래프 탐색, 너무 우선 탐색, 깊이 우선 탐색
문제
<그림 1>과 같이 정사각형 모양의 지도가 있다. 1은 집이 있는 곳을, 0은 집이 없는 곳을 나타낸다. 철수는 이 지도를 가지고 연결된 집의 모임인 단지를 정의하고, 단지에 번호를 붙이려 한다. 여기서 연결되었다는 것은 어떤 집이 좌우, 혹은 아래위로 다른 집이 있는 경우를 말한다. 대각선상에 집이 있는 경우는 연결된 것이 아니다. <그림 2>는 <그림 1>을 단지별로 번호를 붙인 것이다. 지도를 입력하여 단지수를 출력하고, 각 단지에 속하는 집의 수를 오름차순으로 정렬하여 출력하는 프로그램을 작성하시오.
입력
첫 번째 줄에는 지도의 크기 N(정사각형이므로 가로와 세로의 크기는 같으며 5≤N≤25)이 입력되고, 그 다음 N줄에는 각각 N개의 자료(0혹은 1)가 입력된다.
출력
첫 번째 줄에는 총 단지수를 출력하시오. 그리고 각 단지내 집의 수를 오름차순으로 정렬하여 한 줄에 하나씩 출력하시오.
예제 입출력
Algorithm
BFS 1. 그래프 구현 2. 단지에 포함되어있고(graph[x][y] == 1), 방문한 적이 없다면(not visited[x][y]) bfs 수행 -> 이 때 home_cnt 라는 리스트에(각 단지의 아파트 개수) 마지막 원소(0) append 3. bfs 수행하면서 아파트 하나씩 만날 때 마다 home_cnt의 마지막 원소 증가 4. bfs 수행 완료 후 home_cnt의 마지막 원소가 0인 경우(시작 위치 제외하고 만난 아파트가 없는 경우) manually 1 증가 5. home_cnt 리스트 sort 후 length, 원소 하나씩 출력
Code
from collections import deque
queue = deque()
dx = [-1, 1, 0, 0]
dy = [0, 0, -1, 1]
def bfs(graph, sx, sy, visited):
queue.append((sx, sy))
while queue:
x, y = queue.popleft()
for dir in range(4):
nx = x + dx[dir]
ny = y + dy[dir]
# graph 벗어나면 continue
if nx < 0 or nx > N-1 or ny < 0 or ny > N-1:
continue
# 0이면 continue
if not graph[nx][ny]:
continue
# 방문한 적 있으면 continue
if visited[nx][ny]:
continue
# 방문 기록
cnt_home[-1] += 1
visited[nx][ny] = True
queue.append((nx,ny))
N = int(input())
graph = []
for i in range(N):
graph.append(list(map(int, input())))
cnt_home = []
visited = [[False for _ in range(N)] for _ in range(N)]
for x in range(N):
for y in range(N):
queue.clear()
if graph[x][y] and not visited[x][y]:
cnt_home.append(0)
bfs(graph, x, y, visited)
if not cnt_home[-1]:
cnt_home[-1] += 1
cnt_home.sort()
print(len(cnt_home))
for i in cnt_home:
print(i)
티어 : Silver 1 시간 제한 : 1 초 메모리 제한 : 192 MB 알고리즘 분류 : 그래프 이론, 그래프 탐색, 너비 우선 탐색
문제
N×M크기의 배열로 표현되는 미로가 있다.
1
0
1
1
1
1
1
0
1
0
1
0
1
0
1
0
1
1
1
1
1
0
1
1
미로에서 1은 이동할 수 있는 칸을 나타내고, 0은 이동할 수 없는 칸을 나타낸다. 이러한 미로가 주어졌을 때, (1, 1)에서 출발하여 (N, M)의 위치로 이동할 때 지나야 하는 최소의 칸 수를 구하는 프로그램을 작성하시오. 한 칸에서 다른 칸으로 이동할 때, 서로 인접한 칸으로만 이동할 수 있다.
위의 예에서는 15칸을 지나야 (N, M)의 위치로 이동할 수 있다. 칸을 셀 때에는 시작 위치와 도착 위치도 포함한다.
입력
첫째 줄에 두 정수 N, M(2 ≤ N, M ≤ 100)이 주어진다. 다음 N개의 줄에는 M개의 정수로 미로가 주어진다. 각각의 수들은붙어서입력으로 주어진다.
출력
첫째 줄에 지나야 하는 최소의 칸 수를 출력한다. 항상 도착위치로 이동할 수 있는 경우만 입력으로 주어진다.
예제 입출력
Algorithm
BFS 1. 그래프 구현 2. BFS 돌면서 문제 조건에 따라 갈 수 없는 곳이 아니라면 큐에 모두 추가 3. 도착지점에 도착하면 이동 횟수 return
Code
from collections import deque
dx = [-1, 1, 0, 0]
dy = [0, 0, -1, 1]
def bfs(graph, sx, sy, ex, ey, visited):
queue = deque()
queue.append((sx, sy))
while queue:
x, y = queue.popleft()
for dir in range(4):
nx = x + dx[dir]
ny = y + dy[dir]
# 도착지에 도착하면 return
if nx == ex and ny == ey:
return visited[x][y] + 1
# graph 밖으로 나가면 continue
if nx < 0 or nx > N-1 or ny < 0 or ny > M-1:
continue
# 방문한 적 있으면 continue
if visited[nx][ny]:
continue
# 0이면 continue
if not graph[nx][ny]:
continue
# 방문 기록
visited[nx][ny] = visited[x][y] + 1
queue.append((nx, ny))
N, M = map(int, input().split())
graph = []
visited = [[0 for _ in range(M)] for _ in range(N)]
for _ in range(N):
graph.append(list(map(int, input())))
print(bfs(graph, 0, 0, N-1, M-1, visited)+1)
Step 3: 최상단 Node에 방문하지 않은 인접 Node가 있다면 그 Node를 Stack에 넣고 방문 처리
☞ 방문하지 않은 인접 Node가 없으면 Stack에서 최상단 Node 삭제
Step 4: Step 2로 이동
Code
#include <iostream>
#include <stack>
#include <vector>
using namespace std;
#define MAX 7
int visited[7];
vector <int> a[8];
void dfs(int x){
// 이미 방문한 적 있으면 return
if(visited[x]) return;
// 방문 처리
visited[x] = true;
cout << x << " ";
// 인접 노드 방문
for(int i=0;i<a[x].size();i++){
int y = a[x][i];
dfs(y);
}
}
int main(void){
a[1].push_back(2);
a[1].push_back(3);
a[2].push_back(1);
a[2].push_back(3);
a[2].push_back(4);
a[2].push_back(5);
a[3].push_back(1);
a[3].push_back(2);
a[3].push_back(6);
a[3].push_back(7);
a[4].push_back(2);
a[4].push_back(5);
a[5].push_back(2);
a[5].push_back(4);
a[6].push_back(3);
a[6].push_back(7);
a[7].push_back(3);
a[7].push_back(6);
dfs(1);
}
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
#define MAX 7
int visited[7]; // 방문 유무를 표시할 배열
vector<int> a[8];
void bfs(int start){
queue<int> q;
// 시작 노드 큐에 추가
q.push(start);
visited[start] = true;
// 큐가 빌 때까지 반복
while(!q.empty()){
int x = q.front(); q.pop();
cout << x << " ";
// 현재 큐에서 꺼낸 Node와 인접한 노드 모두 방문
for(int i=0;i<a[x].size();i++){
int y = a[x][i];
// 방문한 적 없는 노드이면 큐에 추가 후 방문 처리
if(!visited[y]){
q.push(y);
visited[y] = true;
}
}
}
}
int main(void){
a[1].push_back(2);
a[1].push_back(3);
a[2].push_back(1);
a[2].push_back(3);
a[2].push_back(4);
a[2].push_back(5);
a[3].push_back(1);
a[3].push_back(2);
a[3].push_back(6);
a[3].push_back(7);
a[4].push_back(2);
a[4].push_back(5);
a[5].push_back(2);
a[5].push_back(4);
a[6].push_back(3);
a[6].push_back(7);
a[7].push_back(3);
a[7].push_back(6);
bfs(1);
return 0;
}