1. 객체지향
1-1. 객체지향구현이란?
- 모든 대상을 객체로 나누고, 객체의 행동과 고유한 값을 정의하여 설계하는 방법.
- 캡슐화, 상속, 추상화, 다형성의 특징을 가지고 있음.
- 객체가 또 다른 객체를 호출하거나 상호작용하는 방식으로 수행
ex) 클래스가 다른 클래스를 호출.
-> 일련의 단계를 순차대로 수행하는 절차지향 구현과는 반대되는 개념.
1-2. 장점
- 효율적인 관리 및 성능 향상.
- 전체적인 소프트웨어 복잡성 감소.
- 테스트 및 수정 용이
1-3. 효과적인 객체지향구현을 위해서 어떻게 해야하는가?
- 모듈의 응집도를 높이고 모듈 간 결합도를 낮춘다!
-> 모듈이란?
ㅇ 독립된 하나의 소프트웨어 또는 하드웨어.
ㅇ 객체지향구현에서 모듈은 하나의 객체를 의미하기도 함. ex) 클래스
- 응집도: 모듈 내부에서 구성요소 간에 밀접한 관계를 맺고 있는 정도
- 결합도: 모듈과 모듈간의 관련성/의존 정도
-> 즉, 응집도를 높이면 모듈 내부가 견고해지고, 결합도를 낮추면 모듈의 독립성이 강해져 결과적으로 확장 및 유지보수가 용이.
2. SOLID원칙
2-1. SOLID
- 이러한 객체지향의 4가지 특징을 활용하면서 효과적이고 체계적으로 프로그래밍을 하기위해서 준수해야하는 5가지 원칙.
2-2. 5가지 원칙
- 단일 책임 원칙
- 개방 폐쇄 원칙
- 확장에는 열려있고, 수정에는 닫혀있다
-> 기존의 코드는 변경하지 않으면서 기능을 추가할 수 있어야함.
- 리스코프 치환원칙
- 자식클래스는 언제나 자신의 부모클래스를 대체할 수 있어야 한다.
-> 부모클래스를 대신해 자식클래스로 치환을 해도 우리가 의도하는 대로 구현이 되어야 한다.
-> 기본 클래스의 방향성을 유지!
- 인터페이스 분리원칙
- 인터페이스를 최소 단위로 분리하고, 사용하지 않는 인터페이스에 의존을 하지 않는 원칙
-> 필요한 인터페이스만을 사용.
- 의존성 역전 원칙
- 자주 변화하는 것보단 변화가 없는 것에 의존해야하는 원칙.
- 하이레벨 클래스는 로우레벨 클래스 구현에 의존해서는 안된다.
-> 구체적 클래스가 추상화에 의존하도록함.(역순x)
-> 구체적 클래스보단 인터페이스나 추상클래스에 의존관계를 맺자.
3. 단일 책임 원칙
3-1. 안좋은 예시
//안좋은 예시
class Human
{
//사람관련 기능
public void Eat() { Console.WriteLine("먹다"); }
public void Sleep() { Console.WriteLine("자다"); }
public void Walk() { Console.WriteLine("걷다"); }
//프로그래머관련 기능
public void Develop() { Console.WriteLine("개발하다"); }
public void ReFactoring() { Console.WriteLine("재개발하다");
public void Maintain() { Console.WriteLine("유지하다"); }
}
- Human이라는 클래스안에 일반 사람과 프로그래머 관련 기능을 포함.
-> 하나의 클래스에 두가지 이상의 기능이 존재하기 때문에 단일 책임 원칙에 위배!
3-2. 좋은 예시
//좋은 예시
class Human
{
//사람관련 기능
public void Eat() { Console.WriteLine("먹다"); }
public void Sleep() { Console.WriteLine("자다"); }
public void Walk() { Console.WriteLine("걷다"); }
}
class Programmer
{
//프로그래머관련 기능
public void Develop() { Console.WriteLine("개발하다"); }
public void ReFactoring() { Console.WriteLine("재개발하다");
public void Maintain() { Console.WriteLine("유지하다"); }
}
- 사람과 프로그래머 관련 기능을 다른 클래스로 분류하여, 하나의 클래스가 하나의 기능만 책임지도록 함.
4. 개방 폐쇄 원칙
- 확장에는 열려있고, 수정에는 닫혀있다.
-> 기존의 코드는 변경하지 않으면서 기능을 추가할 수 있어야함.
4-1. 안좋은 예시
public class Human
{
public void Introduce(Programmer programmer, string name)
{
Console.WriteLine($"내이름은 {name}이고, 프로그래머다");
}
}
public class Programmer
{
public string name;
}
public class Designer
{
public string name;
}
- 프로그래머 뿐만아니라 디자이너의 이름을 소개를 하고 싶다면,
Human 클래스 안에 Designer 클래스를 매개 변수로 받는 Introduce함수를 생성하거나
기존의 Introduce함수에 조건을 넣어 수정해야함.
-> 새로운 직업을 가진 클래스가 늘어날수록, Human 클래스를 계속 수정해야하기 때문에 개방 폐쇄 원칙에 위배!
4-2. 좋은 예시
public abstract class Human
{
protected string name;
public abstract void Introduce();
}
public class Programmer: Human
{
public Programmer(string name)
{
this.name = name;
}
public override void Introduce(){
Console.WriteLine($"내이름은 {name}이고, 프로그래머다");
}
}
public class Designer: Human
{
public Designer(string name)
{
this.name = name;
}
public override void Introduce(){
Console.WriteLine($"내이름은 {name}이고, 디자이너다");
}
}
- Human을 추상 클래스로 만들어, 하위클래스에서 Introdouce()함수를 새롭게 정의하도록 바꾸면(오버라이딩), 새로운 직업 클래스를 확장해도 기존의 Human 클래스는 수정할 필요x.
5. 리스코프 치환 원칙
- 자식클래스는 부모클래스를 대체할 수 있어야 한다.
ex) (부모클래스 A 자식클래스B)
A a = new A(); -> A a = new B();
-> 부모클래스를 대신해 자식클래스로 치환을 해도 우리가 의도하는 대로 구현이 되어야 한다.
-> 기본 클래스의 방향성을 유지!
5-1. 안좋은 예시
public class Game
{
public virtual void UseCLanguage()
{
Console.WriteLine("c#사용!");
}
public virtual void DoMeeting()
{
Console.WriteLine("팀프로젝트 회의...!");
}
}
public class Programmer : Game
{
public override void UseCLanguage()
{
Console.WriteLine("c#으로 게임개발!");
}
public override void DoMeeting()
{
Console.WriteLine("개발자 회의 참여했습니다!");
}
}
public class Designer : Game
{
//디자이너에게는 필요없어서 에러처리
public override void UseCLanguage()
{
throw new InvalidOperationException("할 줄 모르는데...?");
}
public override void DoMeeting()
{
Console.WriteLine("기획자 회의 참여했습니다!");
}
}
- Game 클래스 안에는 UseCLanguage()함수와 DoMeeting()함수가 존재.
- Programmer와 Designer는 Game을 상속받음.
- Programmer는 두 기능 모두 필요로 하지만, Designer 는 c언어를 사용할 줄 모르기 때문에 UseCLanguage() 기능이 필요x.
-> 따라서 이를 Designer 함수에서 에러처리 하게되면, Game클래스 또는 상속받은 자식클래스에서 원하는 의도와는 다른 동작을 함.
부모클래스인 Game Class의 방향성에 위배되어 리스코프 치환원칙에 위배!
5-2. 좋은 예시
public class Game
{
public virtual void DoMeeting()
{
Console.WriteLine("팀프로젝트 회의...!");
}
}
public interface ILanguageUsable
{
public void UseCLanguage();
}
public class Programmer : Game, ILanguageUsable
{
public override void DoMeeting()
{
Console.WriteLine("개발자 회의 참여했습니다!");
}
public void UseCLanguage()
{
Console.WriteLine("c#으로 게임개발!");
}
}
public class Designer : Game
{
public override void DoMeeting()
{
Console.WriteLine("기획자 회의 참여했습니다!");
}
}
- 인터페이스 ILanguageUsable을 따로 생성.
- UseCLanguage()함수를 인터페이스 내부로 옮겨 Programmer에게만 상속.
- Programmer와 Designer는 부모클래스 Game을 대체 하여도 문제없이 의도하는대로 구현된다.
6. 인스터페이스 분리 원칙
- 인터페이스를 최소 단위로 분리하고, 사용하지 않는 인터페이스에 의존을 하지 않는 원칙
6-1. 안좋은 예시
public interface IGame
{
public void Meeting();
public void Develop();
public void Design();
}
class Programmer: IGame
{
public void Meeting()
{
Console.WriteLine("회의!");
}
public void Develop()
{
Console.WriteLine("개발!");
}
public void Design()
{
Console.WriteLine("기획..도 내가 해야해?");
}
}
class Designer : IGame
{
public void Meeting()
{
Console.WriteLine("회의!");
}
public void Develop()
{
Console.WriteLine("개발..도 내가 해야해?");
}
public void Design()
{
Console.WriteLine("기획!");
}
}
- 프로그래머와 디자이너 둘다 Game이라는 인터페이스를 상속받고 있다.
- Programmer는 디자인할 필요가 없고, Designer는 개발할 필요가 없다.
-> 사용하지 않는 인터페이스 함수들이 있기 때문에 이는 인터페이스 분리원칙에 위배!
6-2. 좋은 예시
public interface IGame
{
public void Meeting();
}
public interface ICanDevelop
{
public void Develop();
}
public interface ICanDesign
{
public void Design();
}
class Programmer: IGame, ICanDevelop
{
public void Meeting()
{
Console.WriteLine("회의!");
}
public void Develop()
{
Console.WriteLine("개발!");
}
}
class Designer : IGame , ICanDesign
{
public void Meeting()
{
Console.WriteLine("회의!");
}
public void Design()
{
Console.WriteLine("기획!");
}
}
- Game 인터페이스를 더 작은 단위로 나눔.
- 프로그래머와 기획자는 각각 필요한 인터페이스만 상속받아 구현.
7. 의존성 역전 원칙
- 자주 변화하는 것보단 변화가 없는 것에 의존해야하는 원칙.
- 하이레벨 클래스는 로우레벨 클래스 구현에 의존해서는 안된다.
-> 구체적 클래스가 추상화에 의존하도록함.(역순x)
-> 구체적 클래스보단 인터페이스나 추상클래스에 의존관계를 맺자.
7-1. 안좋은 예시
public class Human
{
public void Introduce(Programmer programmer, string name)
{
Console.WriteLine($"내이름은 {name}이고, 프로그래머다");
}
}
public class Programmer
{
public string name;
}
public class Designer
{
public string name;
}
- 개방 폐쇄 원칙을 준수하지 않은 예시를 다시 가져옴.
- 이 코드를 다시보면 Human 클래스에서 Introuduce함수를 호출하려면 매개변수로 Programmer 클래스의 변수를 필요로 함.
- Human 클래스가 Programmer 클래스를 의존하는 형태를 띄게 됨.
-> 하이레벨 클래스 Human이 로우레벨 클래스 Programmer를 의존하기 때문에 의존성 역전 원칙에 위배.
7-2. 좋은 예시
public abstract class Human
{
protected string name;
public abstract void Introduce();
}
public class Programmer: Human
{
public Programmer(string name)
{
this.name = name;
}
public override void Introduce(){
Console.WriteLine($"내이름은 {name}이고, 프로그래머다");
}
}
public class Designer: Human
{
public Designer(string name)
{
this.name = name;
}
public override voide Introduce(){
Console.WriteLine($"내이름은 {name}이고, 디자이너다");
}
}
- 개방 폐쇄 원칙을 준수한 좋은 예시를 다시 가져옴.
- 이 또한 코드를 다시보면, Programmer와 Designer 클래스가 추상클래스인 Human을 상속 받으면서 구체적 클래스가 추상화에 의존하게 됨.
-> 더이상 하이레벨 클래스가 로우레벨 클래스를 의존하는 형태가 아니기 때문에 의존성 역전 원칙을 준수.
-> 주의할 점!!! 추상화할 이유가 없는 클래스들을 일부로 solid원칙에 준수한다고 무리하게 인터페이스나 추상클래스를 늘리는 것은 좋지 않다.
-> 특정 클래스를 상속 관계를 가지고 있는 클래스와 의존관계를 맺을 때, 상위 클래스랑 맺는 것이 좋다는 것이다!
8.번외
8-1. 인터페이스 활용
- 7-2에서 좋은 예시로 보여준 코드를 보면 사람들 중에서도 이름을 밝히고 싶지않거나, 밝히면 안되는 직업을 가지는 사람이 있을 수 있다.
-> 인터페이스를 활용!
public interface IIntroduce
{
public void Introduce();
}
public abstract class Human
{
protected string name;
}
public class Programmer : Human, IIntroduce
{
public Programmer(string name)
{
this.name = name;
}
public void Introduce()
{
Console.WriteLine($"내이름은 {name}이고, 프로그래머다");
}
}
public class Designer : Human , IIntroduce
{
public Designer(string name)
{
this.name = name;
}
public void Introduce()
{
Console.WriteLine($"내이름은 {name}이고, 디자이너다");
}
}
public class SecretAgent : Human
{
public SecretAgent(string name)
{
this.name = name;
}
}
- 인터페이스를 만들어 Introduce를 따로 분리하면, 새로운 직업클래스를 추가하여 Human을 상속받아도 원하는 사람만 소개 가능.
- 하나의 클래스가 하나의 기능만 책임지고 -> 단일 책임 원칙 준수.
- 새로운 클래스를 만들어도 기존의 기능을 수정할 필요 x -> 개방 폐쇄 원칙 준수.
- 작은 단위로 나눠진 인터페이스나 추상클래스로부터 자식 클래스 각자 필요한 기능만을 상속받음.
-> 리스코프 치환원칙, 인터페이스 분리 원칙, 의존성 역전 원칙 준수.
8-2. UML 다이어그램

- is A관계 : 추상클래스와의 관계
- has A관계 : 인터페이스와의 관계