객체 지향 발표 본문

카테고리 없음

객체 지향 발표

violet4795 2019. 8. 22. 17:21

 

객체 지향 설계

 

1.     객체 지향

 

객체 지향 프로그래밍 기법(Object-Oriented Programming, OOP) 이하 객체 지향

컴퓨터 프로그래밍의 패러다임 중 하나

 

1.1   객체지향의 등장 배경(왜?)

 

육하원칙논리에서도 가장 중요한 why를 모른다면 다른건 하나마나 의미가 없죠.

그래서 개념이 왜 등장했는지 짚고 넘어가고자 합니다.

 

우주나 인간이 처음부터 지금의 모습이 설계된 것은 아닙니다. 모든 것들이 환경에 맞게 진화를 거듭해 나가듯이

 

프로그래밍 패러다임도 발전해 나가는 과정을 거칩니다.

 

-> 비구조적 프로그래밍 ( 스파게티소스, GOTO문 )

- 하나의 덩어리에 모든 코드를 때려박는 프로그래밍.. 원시적이고 GOTO문에 의한 흐름제어문에 의존적이다. 

 

-> 절차적 프로그래밍 ( C, C++, 베이직 )

 GOTO문의 의존성을 줄이거나 없애고,

순차, 선택, 반복의 구문들 사용하는 구조적 프로그래밍의 패러다임을 포함하며, 

모듈성과 프로시저로 동작하는 프로그래밍 패러다임

 

-> 객체지향 프로그래밍 ( java, C++, class )

프로세스와 데이터를 통합한 객체라는 개념(Class)을 도입하여 객체가 프로세스와 데이터를 가지게 된다.

 

-> 함수형 프로그래밍 ( JavaScript, 일급객체, 순수함수, etc... )

 

하나의 기능에 하나의 프로세스라는 개념의 절차적 언어들의 특징에서는 잦은 요구사항 변경, 기능수정은 프로세스 재설계뿐 아니라 시스템 재설계로도 이어질 수 있었다.

 

하지만 프로그램을 객체로 보는 객체지향 패러다임의 유행으로 바뀌면서

-기능의 재사용성 향상

-체계적이고 효율적인 코드관리

-기능의 유연성과 확장성

-클래스로 인한 높은 응집력, 약한 결합력

의 이점을 얻었다고 볼 수 있다.

 

 

1.1.1       기본 구성 요소

 

1.1.1.1    클래스(Class)

 

같은 종류에 속하는 속성과 행위를 정의한 것.

누군가 개가 뭐야? 하고 묻는다면 답변할 설명.

객체의 정의 혹은 설명이라고 할 수 있다.

 

Ex) 개는 짖을수 있고 눈이 2개고 발이 4개다

 

class Dog(){

    int foot;

    int eye;



    Dog(){

        foot = 4;

        eye = 2;

    }



    public void bat(){

        System.out.println("멍");

    }

}

 

1.1.1.2    객체(Object)

 

  길가에 있는 개, 집에서 키우는 개들 그 살아있는 아이들을 가리키는 개념

  

 

  //우리집 개이름 똥개

  Dog ddongGae = new Dog();

 

  1.1.1.3    메서드(Method)

클래스로부터 생성된 객체를 사용하는 방법. 

객체에 명령을 내리는 행위

 

 

//똥개 짖기

ddongGae.bat();

 

1.1.2 객체지향 프로그래밍의 특징

 

-> 추상화 ( Abstraction )

불필요한  부분을 무시하고 필요한 공통점만을 다루어, 복잡성을 극복하고 목적에 집중할 수 있도록 하는 것

 

Ex) 아메리카노와 카페라뗴의 추상화 - 커피

     커피와 탄산의 추상화 - 음료

     음료와 햄버거의 추상화 - 음식

keyword) 추상화 수준

     

-> 캡슐화 ( Encapsulation )

객체의 속성(data)과 행위(method)를 묶고, 실제 구현 내용을 외부로부터 감추어 은닉한다.

 

-> 이로 인해 낮은 결합도와 높은 응집력을 유지할 수 있게 해준다.

내부 변수는 private로 구현하여 숨기고 공용메소드만 제공한다.

 

정보 은닉 ( 은닉화 ) 과 혼용되거나 같은 의미로 많이 소개된다. 비슷하지만 다른 개념

 

-> 상속성 ( Inheritance )

상속은 기존의 클래스에 기능을 추가하거나 재정의하여 새로운 클래스를 정의하는것을 의미한다.

이로 인해 클래스의 관계를 맺을 수 있다. 

 

Ex) Audi is a Car

 

상속 된 클래스의 포함관계  

 

-> 다형성 ( Polymorphism )

여러가지 형태를 가질 수 있는 능력

하나의 객체가 여러가지 타입을 가질 수 있는 것 

 

Ex) 

 

// 펫 클래스
public abstract class Pet {
  public abstract void talk();
}


// 자식 클래스

public class Dog extends Pet {

    public void talk(){ System.out.println("멍"); }

}



public class Cat extends Pet {
   public void talk(){ System.out.println("야옹"); }
}

 

public class Main {
  public static void main(String[] args) {
    Pet[] pets = { new Cat(), new Dog(), new Parrot() };
    // 애완동물 세 마리의 울음소리 호출
    for (int i = 0; i < 3; i++){
      // 실제 참조하는 객체에 따라 talk 메서드가 실행된다.
      pets.talk();
    }
  }
}


 

2.     객체 지향 설계란?

 

컴퓨터 프로그래밍에서 SOLID란 로버트 마틴[1][2]이 2000년대 초반[3]에 명명한 객체 지향 프로그래밍 및 설계의 다섯 가지 기본 원칙을 마이클 페더스가 두문자어 기억술로 소개한 것이다. 프로그래머가 시간이 지나도 유지 보수와 확장이 쉬운 시스템을 만들고자 할 때 이 원칙들을 함께 적용할 수 있다.[3] SOLID 원칙들은 소프트웨어 작업에서 프로그래머가 소스 코드가 읽기 쉽고 확장하기 쉽게 될 때까지 소프트웨어 소스 코드를 리팩터링하여 코드 냄새를 제거하기 위해 적용할 수 있는 지침이다. 이 원칙들은 애자일 소프트웨어 개발과 적응적 소프트웨어 개발의 전반적 전략의 일부다.[3]

 

SOLID (객체 지향 설계) - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전.

ko.wikipedia.org

 

2.1   객체 지향 설계라는 개념의 등장 배경

위의 객체지향 예시들에 조금씩 의문을 가질만한 부분들이 숨어있다.

 

와.. 추상화!

 

그럼  아메리카노와 카페라뗴의 추상화 - 커피

     커피와 탄산의 추상화 - 음료

     음료와 햄버거의 추상화 - 음식

도대체 어디까지 추상화되어야 할까

 

와.. 상속!

개 is a 동물 

동물 is a 생물체

생물체 is a object

상속은 객체간의 관계를 만드는 것을 의미하기도 하는데 이것은 코드의 유연성을 해치고,

부모가 변경된다면 자식도 재컴파일해야하는 단점이 있다.

 

그래서 이러한 객체지향적 요소들을 활용하는 정도를 조절하고

좋은 객체지향 설계를 위해서 나온것이 객체지향 디자인의 5원칙(SOLID)이다.

 

상세설명은 여기 너무나도 잘되어있다.

 

그래서 간단한 설명과 함께 간단한 코드로 한눈에 정리하여 볼 수 있도록 재편집하자.

 

 

----------여기까지작성

 

 

--참조 블로그들

...더보기

 

S - SRP - 단일책임원칙 ( Single responsibility principle )

한 클래스는 하나의 책임만 가져야 한다.

 

 

//동물의 이름과 생성자뿐아니라 동물을 파라미터로 받아서 저장까지해주는 DB기능도 가지고있다.
class Animal {
    constructor(name: string){ }
    getAnimalName() { }
    saveAnimal(a: Animal) { }
}



//동물과 동물을 관리하는 기능을 지닌 클래스 분할
class Animal {
    constructor(name: string){ }
    getAnimalName() { }
}
class AnimalDB {
    getAnimal(a: Animal) { }
    saveAnimal(a: Animal) { }
}

 

 

 

...더보기

객체 지향 프로그래밍에서 단일 책임 원칙(single responsibility principle)이란 모든 클래스는 하나의 책임만 가지며, 클래스는 그 책임을 완전히 캡슐화해야 함을 일컫는다. 클래스가 제공하는 모든 기능은 이 책임과 주의 깊게 부합해야 한다.

이 용어는 로버트 마틴이 그의 저서 기민한 소프트웨어 개발과 원칙, 패턴, 실례[1]으로 유명해진 객체 지향 설계 원칙[2]이란 문서의 같은 이름을 가진 단락에서 소개되었다. 로버트 마틴은 이를 톰 디마르코의 책 구조적 분석과 시스템 명세[3]에서 설명한 응집성 원칙에 근거하여 설명하였다.

로버트 마틴은 책임을 변경하려는 이유로 정의하고, 어떤 클래스나 모듈은 변경하려는 단 하나 이유만을 가져야 한다고 결론 짓는다. 예를 들어서 보고서를 편집하고 출력하는 모듈을 생각해 보자. 이 모듈은 두 가지 이유로 변경될 수 있다. 첫 번째로 보고서의 내용 때문에 변경될 수 있다. 두 번째로 보고서의 형식 때문에 변경될 수 있다. 이 두 가지 변경은 하나는 실질적이고 다른 하나는 꾸미기 위한 매우 다른 원인에 기인한다. 단일 책임 원칙에 의하면 이 문제의 두 측면이 실제로 분리된 두 책임 때문이며, 따라서 분리된 클래스나 모듈로 나누어야 한다. 다른 시기에 다른 이유로 변경되어야 하는 두 가지를 묶는 것은 나쁜 설계일 수 있다.

한 클래스를 한 관심사에 집중하도록 유지하는 것이 중요한 이유는, 이것이 클래스를 더욱 튼튼하게 만들기 때문이다. 앞서 든 예를 계속 살펴보면, 편집 과정에 변경이 일어나면, 같은 클래스의 일부로 있는 출력 코드가 망가질 위험이 대단히 높다.

 

-위키백과

 

O - OCP - 개방 - 폐쇄 원칙 ( Open/closed principle )

“소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다.”

 

class Animal {
    constructor(name: string){ }
    getAnimalName() { }
}

//...
const animals: Array<Animal> = [
    new Animal('lion'),
    new Animal('mouse')
];

//여기서 OCP가 위반되고있다. 만약 새로운 동물이 추가된다면..?

function AnimalSound(a: Array<Animal>) {
    for(int i = 0; i <= a.length; i++) {
        if(a[i].name == 'lion')
            return 'roar';
        if(a[i].name == 'mouse')
            return 'squeak';
    }
}
AnimalSound(animals);

//...
const animals: Array<Animal> = [
    new Animal('lion'),
    new Animal('mouse'),
    new Animal('snake')
]
//...
//We have to modify the AnimalSound function:


//새로운 동물때문에 함수가 변경된다.
//새로운 동물이 추가된다면 동물이 확장되면 끝이지만 
//함수코드가 수정되는 것은 바람직하지 못한 모양새
//...
function AnimalSound(a: Array<Animal>) {
    for(int i = 0; i <= a.length; i++) {
        if(a[i].name == 'lion')
            return 'roar';
        if(a[i].name == 'mouse')
            return 'squeak';
        if(a[i].name == 'snake')
            return 'hiss';
    }
}
AnimalSound(animals);



//정상적인 설계
//새로운 동물에 리턴값을 설정하여 수정을 닫고 확장이 열릴 수 있는 코드
class Animal {
        makeSound();
        //...
}
class Lion extends Animal {
    makeSound() {
        return 'roar';
    }
}
class Squirrel extends Animal {
    makeSound() {
        return 'squeak';
    }
}
class Snake extends Animal {
    makeSound() {
        return 'hiss';
    }
}
//...
function AnimalSound(a: Array<Animal>) {
    for(int i = 0; i <= a.length; i++) {
        a[i].makeSound();
    }
}
AnimalSound(animals);

L - LSP - 리스코프 치환 원칙 ( Liskov substitution principle )

“프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다.”

 

void f(){  
    LinkedList list = new LinkedList();
    // …
    modify(list);
}

void modify(LinkedList list){  
    list.add(…);
    doSomethingWith(list);
}

/* 
 *	해당 코드는 별 문제가 없지만, 만약 List가 아닌 속도 개선을 위해 HashSet을 사용해야 하는 상황이 온다면
 *	LinkedList를 다시 HashSet으로 어떻게 바꿀것인가
 * 	모두 Collection인터페이스를 상속하므로 
 */


void f(){  
     Collection collection = new HashSet();
     //…
     modify(list);
}

Void modify(Collection collection){  
     collection.add(…);
     doSomethingWith(collection);
}

 

I - ISP - 인터페이스 분리 법칙 ( Interface segregation principle )

“특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다."

 

interface Shape {
    drawCircle();
    drawSquare();
    drawRectangle();
}

// 인터페이스가 3가지 기능을 합니다.
//도형 3개그리기

//??
//인터페이스를 상속받으면 모든 부분의 구현이 필수적이기 때문에
// 원클래스도 다른 쓸데없는 함수도 구현하는 모습입니다.

class Circle implements Shape {
    drawCircle(){
        //...
    }
    drawSquare(){
        //...
    }
    drawRectangle(){
        //...
    }    
}
class Square implements Shape {
    drawCircle(){
        //...
    }
    drawSquare(){
        //...
    }
    drawRectangle(){
        //...
    }    
}
class Rectangle implements Shape {
    drawCircle(){
        //...
    }
    drawSquare(){
        //...
    }
    drawRectangle(){
        //...
    }    
}


//만약 새로운 함수가 추가된다면?
//상속받은 모든클래스는 다시 drawTriangle()을 구현을 해야할것입니다.
interface Shape {
    drawCircle();
    drawSquare();
    drawRectangle();
    drawTriangle();
}


//수정한 좋은 예입니다.
//인터페이스를 분리하면 쓸데없는 인터페이스 구현이 없어지는 모습입니다.
interface Shape {
draw();
}

interface ICircle {
    drawCircle();
}
interface ISquare {
    drawSquare();
}
interface IRectangle {
    drawRectangle();
}
interface ITriangle {
    drawTriangle();
}
class Circle implements ICircle {
    drawCircle() {
        //...
    }
}
class Square implements ISquare {
    drawSquare() {
        //...
    }
}
class Rectangle implements IRectangle {
    drawRectangle() {
        //...
    }    
}
class Triangle implements ITriangle {
    drawTriangle() {
        //...
    }
}
class CustomShape implements Shape {
   draw(){
      //...
   }
}

 

D - DIP - 의존관계 역전 원칙 ( Dependency inversion principle )

프로그래머는 “추상화에 의존해야지, 구체화에 의존하면 안된다." 의존성 주입은 이 원칙을 따르는 방법 중 하나다.

// 의존관계라는 단어도 좀 어렵게 느껴진다.

// 의존관계를 맺는다. 객체지향에서 객체간에 관계를 정의하는 것은 상속입니다.

// 추상화할때 자주 변하는것과 자주변하지 않는 것이 있을때 변화가 어려운것, 거의 변화가 없는 것에 의존해야 한다는 // 소리입니다.

// https://limkydev.tistory.com/77 

 

// 벤츠가 자신보다 변화가 심한 스노우 타이어에 의존하는 모습

// 여름엔 다른타이어 껴야할 수도 있는데..

 

 

 

 

// 그래서 변화가 없는 타이어라는 인터페이스로 의존관계를 바꿨다.

자신보다 변하기 쉬운 것에 의존하던 것을 추상화 된 인터페이스나 상위클래스를 두어 변하기 쉬운 것의 변화에 영향 받지 않게 의존 방향을 역전시켰다. 즉 벤츠클래스는 타이어 인터페이스에 의존하면서 직접적으로 스노우,일반,광폭 타이어와 의존하는 것을 피했다. 또 스노우, 일반, 광폭 타이어는 기존에는 어떤 것도 의존하지 않았지만, 인터페이스를 의존 해야 한다. 이것이 DIP(의존 역전 원칙)이다. 

 

또 한 가지 알아 둘 것은 상위로 갈 수록 더 추상적이고 변화에 민감하지 않고 수정 가능성이 낮아진다는 사실도 알아두면 좋다. --https://limkydev.tistory.com/77

 

 

--고위 개념으로 갈수록 불변해야 한다.

--자신보다 더 구체적인 것에 의존하지 말아야한다.

 

 

 

3.     디자인 패턴

3.1   디자인 패턴의 개념 등장 배경

-- 나무위키 디자인패턴

...더보기

디자인패턴 개요

 

객체 지향 프로그래밍 설계를 할 때 자주 발생하는 문제들을 피하기 위해 사용되는 패턴. 

여러 사람이 협업해서 개발할 때 다른 사람이 작성한 코드, 기존에 존재하는 코드를 이해하는 것은 어렵다. 이런 코드를 수정하거나 새로운 기능을 추가해야 하는데 의도치 않은 결과나 버그를 발생시키기 쉽고 성능을 최적화시키기도 어렵다. 이로 인해 시간과 예산이 소모된다.  

디자인 패턴은 의사소통 수단의 일종으로서 이런 문제를 해결해준다. 예를 들어 문제 해결의 제안에 있어서도 “기능마다 별도의 클래스를 만들고, 그 기능들로 해야할 일을 한번에 처리해주는 클래스를 만들자.”라고 제안하는 것보다 "Facade 패턴을 써보자."라고 제안하는 쪽이 이해하기 쉽다. 

일반 프로그래머가 만나는 문제가 지구상에서 유일한 문제일 확률은 거의 없다. 이미 수많은 사람들이 부딪힌 문제다. 따라서 전문가들이 기존에 해결책을 다 마련해 놓았다.  

다만 과유불급. 디자인 패턴을 맹신한 나머지 모든 문제를 패턴을 써서 해결하려 드는 패턴병에 걸리지 않도록 조심하자. 디자인 패턴은 알고리즘이 아니다. 어찌 보면 (언어 중립적) 코딩 방법론에 가깝다고 볼수도 있다. 같은 이름의 패턴이 다른 언어로 구현된 모습을 보면 이에 대해 좀 더 쉽게 이해할 수 있을 것이다. 

 

 

저의 개념으로 번역

 

객체지향에서 여러 문제들을 해결할 때 누군가는 해결했을 것이고 비슷한 생각을 해보았을 것입니다. 

 

롤하다가 원딜은 치명타 템트리를 가는 등의 일정한 규칙이 생겨날 수도 있고

 

집에 가는 길에도 집에가는 방향이나 버스를 타는 규칙등이 생겨날 것입니다.

 

이것들을 실제로 하면서 느끼는 것보다 

 

내가 미리 템트리를 알고있다면? 내가 어떻게하면 집에 빨리 가는지 알고있다면?

훨씬 효율적일 것이기 때문에 배웁니다.

 

하지만 패턴만 믿다가

아무도 하지않은 참신한 템트리, 누군가 해보았지만 빛을 보지 못한 조합,

맛 없을것 같아서 해보지 않지만 해보니 맛있는 음식 레시피

이런 반례들이 있으니 너무 맹신하진 말아야합니다.

 

면접관도 모든 디자인패턴을 완벽하게 설명하긴 힘들것입니다.

여기서는 몇가지 대표적인 디자인패턴 몇가지패턴을 간단하게 소개하고자 합니다.

 

3.2 디자인패턴 분류

 

--https://gmlwjd9405.github.io/2018/07/06/design-pattern.html

 

GoF(Gang of Fout)

  • GoF 디자인 패턴
    • GoF(Gang of Fout)라 불리는 사람들
    • 에리히 감마(Erich Gamma), 리차드 헬름(Richard Helm), 랄프 존슨(Ralph Johnson), 존 블리시디스(John Vissides)
    • 소프트웨어 개발 영역에서 디자인 패턴을 구체화하고 체계화한 사람들
    • 23가지의 디자인 패턴을 정리하고 각각의 디자인 패턴을 생성(Creational), 구조(Structural), 행위(Behavioral) 3가지로 분류했다.

- 싱글톤

 

- 해당 클래스의 인스턴스가 하나만 생성이 되는것을 보장하고, 어디서든지 그 인스턴스에 접근이 가능하도록 만드는 패턴

- 어플리케이션이 시작될 때, 어떤 클래스가 최초 한번만 메모리를 할당하고(Static) 그 메모리에 인스턴스를 만듬 

  = 그 이후에 생성자 시도한다면, 최초에 생성된 객체를 리턴하도록 함 (getInstance())

  = 객체가 다른 방법으로 생성되지 않도록 생성자를 Private로 구현한다.



출처: https://ryusae.tistory.com/6 [초보자 전용 마을]

 

 

public class Singleton{
	// 인스턴스를 저장하기 위한 변수
	// 외부에서 직접 호출할 수 없도록 private으로 선언
	private static Singleton uniqueInstance;
	
	//생성자도 private으로 선언하여 외부에서 호출할 수 없도록 함
	private Singleton(){}
	
	//클래스의 유일한 인스턴스를 반환하는 메서드
	public static Singleton getInstance(){
		//인스턴스가 존재하지 않는다면 생성
		if(uniqueInstance == null){
			uniqueInstance = new Singleton();
		}
		return uniqueInstance;
	}	
	
}

 

- 메모리 낭비 방지 : 한번만 new 로 인스턴스를 생성하기 때문

: 객체를 생성하게 되면 그 클래스의 인스턴스는 Heap 메모리에 올라가게 되고, 그 인스턴스를 가리키고 있는 변수는 Stack 메모리 영역에 생기게 된다. 이러한 작업 자체가 시간이 걸리는 일이며 한 객체를 여러번 new하게 되면 시간이 더욱 오래 걸리게 된다.

그래서 자주 사용되는 객체는 한번만 생성한 후, Heap에 존재하는 이 객체를 가리키도록만 만든다. 

=> 즉, 객체가 생성될 때 Heap 영역에 올라가는 시간과 메모리를 줄일 수 있다.

- 전역 인스턴스 이므로, 다른 클래스의 인스턴스에서 데이터 공유 용이

- 공통된 객체를 여러개 생성하여 사용하는 상황에서 유용 : ex) DB Pool

- 그 인스턴스를 오직 '한개'만 존재하는 것을 보증할 때 사용

- 두번째 호출 시 부터는 객체 로딩시간이 줄어서 성능이 좋아짐

- 불필요한 변수들로 전역 네임스페이스를 오염시키지 않음

- 상속이 가능하여 간단하게 사용 가능

- 실행시간에 초기화 됨 

: 게으른 초기화를 사용하는 것은 정적 변수와 차이가 있다 : 정적멤버변수는 정적 초기화 및 컴파일러가 변수의 초기화 순서를 보장하지 않으므로, 정적 변수끼리의 의존 불가능


출처: https://ryusae.tistory.com/6 [초보자 전용 마을]

 

 

-팩토리 메서드

 

 

 

 

4. 면접이라면..?

면접에서 객체지향은 단골손님입니다.

 

객체지향이란 개념자체를 완벽하게 아는것도 중요합니다. 하지만

해당 개념을 아는 것만으로 '객체지향적인 설계를 할 수 있다.' 라고 말하기 힘들며,

 

많은 경험이 토대가 되어야 합니다.

 

그래서 어떤 주제를 주고 '어떻게 설계를 할 것 인가'라고 문제를 내고 면접자가 어떻게 풀어나가는지

과정과 왜 그렇게 설계했는지에 대한 질문면접을 보는 경우가 많습니다.

 

흔히 아는 지식이 아니라 자신이 생각하는 객체지향이란?

 

객체지향 언어의 특징 몇가지만 말씀해주세요

 

MFC와 java의 차이점이 뭔가요?

 

커피자판기 구상하신다면 어떻게 짜실꺼에요?

 

디자인 패턴에서는

 

자신이 아는 디자인 패턴 몇가지를 소개해보세요.

 

 

 

5. 문제풀이

7.4

객체 지향 원칙에 따라 주차장을 설계하라.

public enum VehicleSize { Motorcycle, COmpact, Large}

public abstract class Vehicle {
	protected ArrayList parkingSpots = new ArrayList();
    protected String licensePlate;
    protected int spotsNeeded;
    protected VehicleSize size;
    
    public int getSpotsNeeded() { return spotsNeeded; }
    public VehicleSize getSize() { return size; }
    
    /* 주어진 주차 공간에 차량 주차( 다른 차량 사이에 주차 가능함) */
    public void parkInSpot(ParkingSpot s) {parkingSpots.add(s); }
    
    /* 차를 뺀 다음 해당 공간이 비어있다고 알려준다. */
    public void clearSpots() {...}
    
    /* 주차하려는 차량을 수용할 수 있는 공간이 있는지, 비어 있는지 확인한다.
      	단순히 크기만 비교한다. 주차 장소가 충분한지는 확인해주지 않는다. */
   	public abstract boolean canFitInSpot(ParkingSpot spot);
}

public class Bus extends Vehicle {
	public Bus() {
    	spotsNeeded = 5;
        size = VehicleSize.Large;
    }
    
    /* 주차 공간이 큰지 확인. 주차 공간 수는 확인하지 않는다. */
    public boolean canFitInSpot(ParkingSpot spot) {...}
}

public class Car extends Vehicle {
	public Car() {
    	spotsNeeded = 1;
        size = VehicleSize.Compact;
    }
    
    /* 공간 크기가 주차하기에 알맞은지 확인 */
    public boolean canFitInSpot(ParkingSPot spot) {...}
}

public class Motorcycle extends Vehicle {
	public Motorcycle() {
    	spotsNeeded = 1;
        size = VehicleSize.Motorcycle;
    }
    
    public boolean canFitInSpot(ParkingSPot spot) {...}
}   

public class ParkingLot {
	private Level[] levels;
    private final int NUM_LEVELS = 5;
    
    public ParkingLot() {...}
    
    /* 주차공간(여러개에 걸쳐)에 주차한다. 실패하면 false를 반환. */
    public boolean parkVehicle(Vehicle vehicle) {...}
}

/*주차장 내의 한 층을 표현 */
public class Level {
	private int floor;
    private ParingSpot[] spots;
    private int availableSpots = 0;
    priavte static final int SPOTS_PER_ROW = 10;
    
    public Level(int flr, int numberSpots) {...}
    
    public int availableSpots() { return availableSpots; }
    
    /*주어진 차량을 주차할 장소를 찾는다. 실패하면 false 반환한다. */
    public boolean parkVehicle(Vehicle vehicle) {...}
    
    /* 차량을 spotNumber가 가리키는 장소부터 vehicle.spotsNeeded만큼의 공간에 주차한다. */
    private boolean parkStartingAtSPot(int num, Vehicle v) {...}
    
    /* 주차할 장소를 찾는다. 빈자리 인덱스를 반환하거나 실패했을 경우 -1을 반환한다. */
    private int findAvailableSpots(Vehicle vehicle){...}
    
    /*차를 빼면 availableSpots을 증가시킨다. */
    public void spotFreed() {availableSpots++; }
    
}

public class ParkingSpot {
	private Vehicle vehicle;
    private VehicleSize spotSize;
    private int row;
    private int spotNumber;
    private Level level;
    
    public ParkingSpot(Level lvl, int r, int n, VehicleSize s) {...}
    
    public boolean isAvailable() {return vehicle == null;}
    
    /*주차 공간이 충분히 크고 사용 가능한지 확인한다. */
    public boolean canFitVehicle(Vehicle vehicle) {...}
    
    /* 해당 공간에 차를 주차한다. */
    public boolean park(Vehicle v){ ...}
    
    public int getRow(){ return row;}
    public int getSpotNumber() { return spotNumber; }
    
    /*차를 뺀 다음 주차공간이 새로 생긴 층의 위치를 알린다. */
    public void removeVehicle() { ...}
}