프로세스 모델(3. 설계)
- 거시적 : architecture(component module + 관계) 설계
- 미시적 : module, UI, DB(정규화 - normalization) 설계
- 설계의 목표*
효율성 - 빠름, 단순성 - not dirty, 복잡하지않게 (for 유지보수) - 효율성(efficiency)
- 처리시간과기억공간
- 단순성(simplicity)
- 유지보수성에 영향을 주는 가장 중요한 특성
- 빠르면서 메모리 덜 쓰기*
모듈화(분할을 잘 하는 것)
- 추상화 : 자료(data)/ 기능/제어(method) 추상화
- 결합도(coupling) : 모듈간 상호 의존 정도
- data coupling
- stamp coupling
- control coupling
- common coupling
- contact coupling
- 응집도(cohesion) : 해당 모듈의 단일성정도
- Functional cohesion
- Sequential cohesion
- Communicational cohesion
- Procedure/Temporal/Logical
**용어**
- 모듈 : 정적인 구현 단위(compile 단에서도 외부 영향 없음)
- 컴포넌트 : 런타임에 독립적으로 배포되고 실행되는 단위
- 컴포넌트(모듈) : 클래스의 집합체
- 서브 시스템 : 시스템 복잡도를 줄이기 위해 분할한 것
- 시스템(여러개의 실행 파일)을 분할 = 서브시스템
- 서브시스템(실행파일)을 분할 = 컴포넌트(모듈)
❓요구분석은 "what"에 집중, 설계는 "How"에 집중
설계 작업 과정
- 남이 잘해놓은(reference style)을 참고
- 특정 도메인 별 reference style이 있음
전통적인 설계 원리
효율성, 단순성 중요
추상화
- 내가 필요한 특정 시스템에 대한 정보에만 집중
- 대상에 대하여 특정한 목적에 관련된 정보에 집중하고 나머지 정보는 무시하는 관점
- 데이터나 절차적인 동작 관점으로 정의
- 클라이언트와 서버의 정보교환을 주고받는 메시지의 추상화
- 클라이언트와 서버의 정보교환을 주고받는 메시지의 추상화
- 데이터나 절차적인 동작 관점으로 정의
캡슐화
시스템의 핵심특성에 초점을 두어 하나의 큰 시스템을 분할하는 원리
- 추상화된 대상이 제공하는 서비스를 쉽게 접근하게 하는 개념
- 정보 은닉 - data는 숨기고 method는 열어서 method를 통해 data 접근
모듈화
- 분할(클래스든, 패키지 레벨이든, 서브 시스템 레벨이든)
- 문제를 소프트웨어의 구성요소가 될 만한 수준으로 분할하는 과정
- 소프트웨어를 작은 구성 요소, 즉 패키지 또는 클래스로 나누는 것
- 추상화–시스템의 핵심특성에 초점을 두어 하나의 큰 시스템을 분할하는 원리
- 캡슐화–분할된 핵심 정보만을 노출
- 큰 시스템을 잘 모듈화된 시스템으로 완성
Coupling(결합도) vs Cohesion(응집도)
Coupling : 모듈간의 연결성(상호) Cohesion : 모듈 내부의 응집도(action에 대한 동사가 하나만 있으면 best) + 서로 배치된 개념 + coupling을 낮추면(좋게 만들면) cohesion이 낮아짐(나빠짐) + 둘중 어떤 부분을 더 신경쓸지는 개발자가 판단 + 서로 적당히 타협할 수 있는 중간수준을 판한할 필요가 있음 + 응집도가 높아지도록, 결합도가 낮아지도록 할것
Coupling(결합)
낮아야 좋음
- 모듈간에 서로 의존하는 정도
- 모듈 간 인터페이스 수
- 각 인터페이스의 복잡성(통신 유형에 따라 결정됨)
- 모듈 간 연결은 아래의 여러 종류로 이뤄짐
- Coupling이 높으면 하나를 바꾸면 다른곳도 다 바꿔야 해 유지보수가 불편
- (아래로 내려갈수록 Coupling이 높아 별로임, 암기해야함)*
데이터 결합(data coupling)
- 모듈 들이 주고받는 매개변수가 간단한 타입이거 나 레코드 안의 필드이더라도 단순 타입인 경우(단순 int)
//데이터 결합 예시
//모듈 A
public void foo() {
int result = makeSquare(5);
}
//모듈 B
public int makeSquare(int x) {//int같은 단순타입이 전달됨
return x*x;
}
스탬프 결합(stamp coupling)
- 복합 데이터 구조의 일부만 사용하는 모듈에 복합 데이터 구조체 전달할때(배열같은 구조체)
//스탬프 결합 예시
//모듈 A
public void foo() {
// 이름과 이메일 주소를 생성자 함수에 전달
Person p = new Person("홍길동“, xyz@uos.ac.kr);
sendEmail(p);
}
//모듈 B
Public void sendEmail(Person person){ //객체가 전달됨
// 이메일 발송하는 코드 삽입
}
(여기까진 대부분 사용(적당한 수준의 coupling))
제어 결합(control coupling)
- 한 모듈이 다른 모듈의 제어흐름 경로를 결정
public void foo() {
printCharge(true);
}
//받은 변수에 의해 분기 or 반복 or 순차 하게됨
public void printCharge(Boolean isMember){
if (isMember) {
printMemberCharge();
}
else {
printNormalCharge();
}
}
공통 결합(common coupling)
- 한 모듈이 다른 모듈이 읽은 전역 변수 값을 쓰거나변경
- 공유할 수 있는 변수에 의해 모듈이 묶이게 됨
- 정적변수의 값의 보장이 힘들어짐
- common coupling은 꼭 써야하는 상황이 있을 수 있지만 최대한 지양해야함
- c언어의 전역변수
- java의 static변수
class Example1 {
static int a = 5; //정적변수 역할(전역 변수와 유사)
int b;
}
class Example2 {
...
public void methodA(){
Example1.a = 10;
}
}
class Example3 {
...
public void methodB() {
System.out.println(Example1.a);
}
}
내용 결합(content coupling)
- 한 모듈이 다른 모듈의 내용을 직접 참조
- 객체지향 모델에서는 존재할 수가 없음
Cohesion
높아야 좋음
- 하나의 모듈 안에서 수행되는 작업들이 서로 관련된 정도
- 모듈 안의 여러 요소들이 하나의 목적을 위하여 유기적으로 관련되어 있는 것이 제일 좋음
- 높은 응집
- 재사용하기도 쉽고 이해하기도 좋으며
- 수정에 의하여 받는 영향이 적다
(아래로 내려갈수록 Cohesion이 떨어짐, 순서 암기(레벨))
기능적(functional) 응집
- 하나의 기능에 모두 기여하고 밀접하게 관련
- 하나의 동사/목적
- 가장 이상적
class Example {
...
public double cosine(double radian) {
//cosine 값을 계산하는 코드 포함
...
}
}
순차적(sequential) 응집
- 소작업 결과가 다음 소작업의 입력
- 소작업이 2개 이상
- 각 소작업의 순서를 바꿀 수 없음
- 즉 쪼개지 않아도 괜찮음
class Example {
...
public void method(){
String content = readFile(); //앞 소작업의 결과물
writeFile(content); //앞 소작업의 결과물이 입력됨
...
}
}
교환적(communicational) 응집
- 모듈의 내부 요소들이 동일한 데이터를 조작하기 때문에 그룹화 된 경우
- 소작업의 교환법칙 성립
- 소작업이 독립적
- 하나의 모듈에 구지 넣을 필요가 있을까?
- 쪼개도 상관없지 않나?
- 즉, 기능적 응집으로 될 가능성이 있음
class Example { ...
public void method(Person p){
actionA(p);
actionB(p);
...
}
}
절차적(procedural) 응집
- 모듈 안에서 수행되는 연산이 프로그램에서 수행되는 순서와 관련
- 소작업들이 제어신호가 전달되어 묶임
- control coupling을 안하려고 하면 procedural이 됨
- control coupling을 할지 procedural cohesion을 할지는 개발자 선택
class Example {
...
public void method(){
Boolean res = actionA();
if (res == true) actionB();
else actionC();
...
}
}
시간적(temporal) 응집
- 프로그램 실행의 특정한 시간에 처리되므로 한 그룹안에 모여있는 경우
- 작업의 효율을 위해 모아둠
- 초기화 작업,특정 시점에 한 번만 수행되는 소작업의 묶임
- 같은 시기에 동작되는 모든 동작
- 초기화 작업
class Example { ...
public void initialize() {
//초기화작업을 위한 다수 의 기능 포함
actionA();
actionB();
actionC();
actionD();
...
}
}
논리적(logical) 응집
- 본질적으로 다르더라도 같은 범주의 기능을 수행하므로 논리적으로 분류
- 같은 카테고리에 묶을 수 있는 기능을 합쳐놓은 응집
- 유사 성격 소작업의 묶음
- 예)제반입력/출력을모두수행하는모듈:diski/o->tapei/o->...
class Example {
...
public void someMethod(int val) {
//유사한 성격을 가지거나 하나의 부류에 묶이는 기능을 포함
switch(val){
case 0:
do actionA //예) 디스크 입출력
break;
case 1:
do action //예) USB 입출력
break;
default: break;
}
}
우연적(coincidental) 응집
- 단위 안의 요소들이 의미적으로 아무 관계가 없음
- 이유도 없고 아무생각없이 묶은 응집
- 비유사 성격 소작업의 묶음
객체지향 설계 원리
SOLID
상속과 인터페이스 등 새로운 구문과 함께 발전
단일 책임의 원리(Single Responsibility Principle) 개방 폐쇄의 원리(Open Close Principle) 리스코프 교체의 원리(Liskov Substitution Principle) 인터페이스 분리의 원리(Interface Segregation Principle) 의존관계 역전의 원리(Dependency Inversion Principle)
1. 인터페이스와 구현의 분리
- 인터페이스
- 공개된 메소드의 프로토타입만을 정의해 놓은 것
- 통용될 수(모두가 사용하는) 메서드만 정의
- 공개된 메소드를 인터페이스로 따로 정의하고 이를 구현 상속
- 컴포넌트의 공개 인터페이스를 컴포넌트가 어떻게 구현되는지 상세하게 나타낸 것과 분리
compareTo(a, b)
=> a와 b의 객체를 비교
Comparable Interface를 상속받아 해당 클래스(classA)에 상속시켜 구현을 해야함(이때 메서드 명은 Interface의 메서드명을 Override)
2. 단일 책임의 원리
- 클래스의 역할과 책임을 단일화 하여 클래스를 변경해야 할 이유를 하나로 제한
- 텍스트를 고치는 책과 텍스트를 출력하는 책 2가지의 종류(책임)이 있다고 판단 => 2개로 쪼개기
3. 개방 폐쇄의 원리(Open-Close Principle)
- 소프트웨어 개체(클래스, 모듈, 기능 등)가 확장을 위해서는 열려있어야 하지만 수정을 위해서는 닫혀야 함
- 상속 - 즉, 다형성의 원리를 잘 따르라는 뜻
- 다형성이 적용되어 서로 대체할 수 있는 인터페이스를 구현
- 메서드를 상속받은자의 상황에 따라 확장 가능(특정 상황에 따른 구현이 가능)
- 아래의 그림에서 확장에 닫힘 => 확장에 열림으로 수정
- 인터페이스는 수정에 닫힘(Client는 SortAlgorithm을 의존하는 클래스)
- SortAlgorithm이 바뀔때(메서드명이 바뀌거나...) Client도 바꿔야 하기에 유지보수에 불편
- Client가 SortAlgorithm을 의존한다 라는 개념에서 Client
4. 리스코프 교체의 원리(Liskov Substitution Principle)
클래스 설계시 상속관계를 만들때 함부로 만들지 말아라.
- 클래스 B가 클래스 A에서 상속받은 하위 유형이라면 프로그램의 동작을 방해하지 않고 A를 B로 대체
- 하위 클래스는 클라이언트의 관점에서 기능을 손상시키지 않는 방식으로 상위 클래스 메소드 를대체
- 하위 클래스는 상위 클래스의 규약을 엄격하게 지켜야 함
- Rectangle이 상위 클래스, Square가 하위 클래스
- Square는 정사각형이라 width와 height가 똑같음
- 논리적으로 문제는 없지만 구지 필요없는 속성이 상속에 의해 추가됨
- 같은 값을 2가지로 표현할수 있게되면서 값을 보존할 수 없게될 수 있음
- width와 height가 아닌 length라는 변수 하나만 두고 하는 것이 옳음
예시)
사람 - 학생, 교수, 교직원 (좋은 예시)
학생 - 1학년, 2학년, 3학년, 4학년(나쁜 예시)
- why? 1학년, 2학년과 같은 subclass가 superclass의 +@ 할만한게 없음
- 즉, 구지 상속을 하지 않고 학생이라는 클래스의 속성으로 학년을 구분하기만 하면 충분함
5. 인터페이스 분리의 원리(Interface Segregation Principle)
- 클라이언트가 사용하지 않는 인터페이스를 강제로 구현해서는 안됨
- 비만 인터페이스(fat interface)
- 오염된 인터페이스(polluted interface)
비만 인터페이스
- Animal이라는 인터페이스에 fly(), work(), eat(), work()가 있음
- Animal을 상속받은 클래스는 위 4 메서드를 전부 구현해야 함.
- People이 상속받았다고 하면 fly()는 부적절한 메서드
- 쓸데없는 함수를 구현한 인터페이스
- 컴팩트하게 필요한 것만 구현한 것이 아닌 모든 것을 아우르려고 욕심내다보니 구현받는 입장에서 부적절한 메서드가 존재
오염된 인터페이스
- 공통분모가 아닌 합집합을 넣어버린 인터페이스
6.의존관계 역전의 원리(Dependency Inversion Principle)
- 구체화 된 모듈이 추상화 된 모듈에게 의존이 역전되도록 설계
+ Character라는 클래스가 존재 + 한손검, 양손검, 해머, 도끼 등의 무기를 포함(의존)
class Character{
final String name;
int health;
OneHandSword weapon;
Character(String name, int health, OneHandSword weapon){
this.name = name;
this.health = health;
this.weapon = weapon;
}
...
}
- one hand sword의 객체를 받아서 Character가 동작중
- 만약 한손검이 아닌 도끼를 쓰고싶다면 OneHanSword가 아닌 BattleAxe를 사용해야함.
- 구체화된 클래스에 의존화된 클래스를 만들 때는 해당 클래스 자체를 의존x
- 구체화된 클래스에 의존하게 하지말고 해당 클래스를 추상화 하여 해당 추상화된 클래스를 상속하게 하라
- onehandsword가 Character에게 의존 받고 있었는데 onehandsword가 weaponable을 의존하는 걸로 관계가 역전됨(의존 받다가 의존 하게 됨)
설계 메트릭
설계에 대한 정량적 평가를 위한 측정치
- 설계 결과를 어떻게 평가할 것인가?
전통적인 메트릭
- 크기
- 복잡도
- 결합도
- 응집도
- 정보흐름
객체지향 소프트웨어의 품질 메트릭
1. 클래스 당 가중 메서드
- $WMC(C) = C_{m1} + C_{m2} + ... + C_{mn}$
- $C_{mn}$ = 클래스 C에 있는 메서드 복잡도

2. 자식 노드의 개수(Number of Children)
- 클래스의 상속 구조에서 직계 자식 클래스의 수
- 이것이 많으면 좋은지 적으면 좋은지는 평가가 불가능
- 그저 평균적으로 얼마정도 나오는가 확인하기 위해 사용
- NOC = 0 : Point, Square, LinkListNode, PolygonNode, Triangle
- NOC = 1 : Rectangle
- NOC = 2 : Polygon
3. 상속 트리의 깊이(Depth of Inheritace Tree)
- 상속 트리의 루트로부터 해당 클래스까지 가장 깊은 상속 경로
- DIT(C) = 0 : 부모 클래스가 없는 경우
- DIT(C) = 0 : Point, LinkListNode, PolygonNode, Polygon
- DIT(C) = 1 : Rectangle, Triangle
- DIT(C) = 2 : Square
4. 객체 클래스 사이의 결합(Coupling Between Object Classes)
- 해당 클래스가 의존하고 있는 클래스의 개수
- 해당 클래스가 타 클래스의 메서드나 데이터를 사용하면 의존
- 상속은 의존x
- CBO = 0 :
- CBO = 1 : Point(Polygon)
- CBO = 2 : Polygon(Point, LinkListNode)
- Rectangle과 Triangle, Square은 Polygon을 상속하므로 영향 x
- CBO = 3 : LinkListNode(자기자신, Polygon, PolygonNode)
- CBO가 클수록 이해하고 테스트하고 유지보수 및 재사용이 어려움
5. 클래스의 책임(Responsiblity for a Class)
- 책임 = 임무(메서드)
- 클래스의 메서드 개수 + 그 클래스의 메서드가 호출하는 메서드 개수
6. 메서드 응집 결핍
- 해당 클래스의 속성을 공유하지 않는 메서드 쌍의 수
- 교재에 나온 공식 부분은 무시
- deprecated
- 그림을 보고 계산
- 메서드 내에 응집이 잘 된 경우와 잘 안된경우
- 호출관계와 참조관계를 그래프로 그렸을때 다 서로 연관되어 하나로 그려지면 오른쪽 그림
- (A,B,x), (C, D, E, y)로 쪼갤수 있는 그림이 왼쪽그림
- 이렇게 쪼개진다면 2개의 클래스로 쪼갤 수 있음에도 합쳐진 클래스라는 뜻
- 응집이 잘될 수록 LCOM값이 작음
- x와 y는 데이터 변수
- A, B는 소작업
- A가 B를 호출(참조)
'소프트웨어 공학' 카테고리의 다른 글
소프트웨어 공학 7(설계) (0) | 2023.06.12 |
---|---|
소프트웨어 공학 6(설계) (0) | 2023.05.31 |
소프트웨어 공학 4(요구분석) (0) | 2023.05.06 |
소프트웨어 공학 3(요구분석) (0) | 2023.05.06 |
소프트웨어 공학 2(계획) (0) | 2023.05.06 |