자바의 객체 지향

포모·2020년 12월 1일
0

JAVA의 기본

목록 보기
3/9

객체지향 프로그래밍이란?
객체지향 프로그래밍(Object-Oriented Programming)은 프로그램을 설계하는 개념이자 방법론입니다.
줄여서 OOP라 부르며, 프로그램(실제세계)에서 객체(사물)이라는 기본 단위로 나누어 이 객체들 간 상호작용을 기본 개념으로 합니다.


OOP는 재사용성이 용이하여 대규모 프로젝트에서 많이 사용되는 방법론입니다.
프로그램 개발과 유지보수가 용이하다는 점이 가장 큰 장점입니다.


🎯 Class

public class Animal{

}

위의 Animal 클래스는 객체를 만드는 기능을 가지고 있습니다.

Animal cat = new Animal();

new를 이용하여 객체를 생성할 수 있습니다. catAnimal 클래스의 인스턴스(instance)인 객체입니다.

객체와 인스턴스
클래스에 의해 만들어진 객체를 instance라고 합니다.


객체와 인스턴스의 차이점은?🤔

  • Animal cat = new Animal() : cat은 객체이다.
  • instance : 특정 객체(cat)가 어떤 클래스(Animal)의 객체인지 관계 위주의 설명을 할 때 사용됩니다.

  • 과자틀 : 클래스
  • 과자틀에 의해 만들어진 과자 : 객체 (Object)

Instance variable

public class Animal {
    String name;
}

위와 Animal 클래스에 선언된 String 변수처럼 클래스에 선언된 변수를 객체 변수라고 합니다.
도트 연산자(.)을 이용하여 접근할 수 있습니다.

System.out.println(cat.name);		// null

Method

객체 변수에 값을 대입하는 방법에 있어 가장 보편적이 방법이 method를 이용하는 방법입니다.


public class Animal {
    String name;

    public void setName(String name) {
        this.name = name;
    }
}

thisAnimal 클래스에 의해 생성된 객체를 지칭합니다.
cat 객체가 setName()을 호출했다면 thiscat 객체를 지칭하게 됩니다.


객체 변수는 공유되지 않는다.

public static void main(String[] args) {
     Animal cat = new Animal();
     cat.setName("boby");

     Animal dog = new Animal();
      dog.setName("happy");
  }

cat.namedog.name을 출력하면 각각 babyhappy가 출력됩니다.
즉, name 객체 변수는 공유되지 않는다는 것을 확인할 수 있습니다.


객체 변수의 값이 독립적으로 유지된다는 점은 클래스 존재의 이유이자 객체 지향이 가지는 의의라고 볼 수 있습니다.


객체 변수의 값은 공유되지 않지만 static을 이용하여 객체 변수를 공유하도록 만들 수도 있습니다.


🎯 Method

method는 똑같은 내용의 반복을 피하기 위해 재사용할 수 있는 부분을 작성하는 것입니다.

public int sum(int a, int b) {
    return a+b;
}

이와 같이 "두 수를 더해주는 작업"이라는 내용의 코드를 반복하지 않게 해줍니다.


🎯 Call by value

class Updater {
    public void update(Counter counter) {
        counter.count++;
    }
}

매개변수인 counter의 객체 변수 count의 값을 증가시켜주는 update 메서드가 있다고 가정해보겠습니다.

Counter myCounter = new Counter();
System.out.println("before update:"+myCounter.count);	// 0
Updater myUpdater = new Updater();
myUpdater.update(myCounter);
System.out.println("after update:"+myCounter.count);	// 1

객체를 통해 전달받은 경우 객체 변수의 값도 변경됩니다.

public void update(int counter){
	counter.count++;		// 값이 변경되지 않음
}

만약 update()의 매개변수 자료형의 객체가 아니었다면 변경될 수 없습니다.


🎯 Inheritance

public class Animal {
    String name;

    public void setName(String name) {
        this.name = name;
    }
}

public class Dog extends Animal {
	public static void main(String[] args) {
        Dog dog = new Dog();
        dog.setName("poppy");
        System.out.println(dog.name);	// poppy
    }
}

Animal 클래스를 상속하는 Dog 클래스가 있다고 가정해 보겠습니다. (이때 상속은 extends라는 키워드를 사용합니다.)
Dog 클래스는 상속받은 setName() 메소드를 사용할 수 있습니다.


IS-A 관계

DogAnimal 클래스를 상속 받았으므로 Animal의 하위 개념이라고 볼 수 있습니다.
자바는 이러한 관계를 Dog is a Animal, 즉 IS-A 관계라고 합니다.


Animal dog = new Dog();			// enable
Dog dog = new Animal(); 		// compile error

부모 클래스인 Animal을 자식 클래스인 Dog가 자료형으로 사용할 수는 없습니다.


자바에서 만드는 모든 클래스는 Object라는 클래스를 상속받게 되어있습니다.
Object 클래스를 상속하도록 코딩하지 않아도 자바에서 만들어진 모든 클래스는 Object 클래스를 자동으로 상속받게끔 되어있습니다.

따라서 자바에서 만드는 모든 객체는 아래와 같이 Object 자료형으로 사용할 수 있습니다.

Object animal = new Animal();
Object dog = new Dog();

Method Overriding

public class Dog extends Animal {
    public void sleep() {
        System.out.println(this.name+" zzz");
    }
}

public class HouseDog extends Dog {
    public void sleep() {		// overriding
        System.out.println(this.name+" zzz in house");
    } 

    public static void main(String[] args) {
        HouseDog houseDog = new HouseDog();
        houseDog.setName("happy");
        houseDog.sleep();		// happy zzz in house
    }
}

부모 클래스의 메소드를 자식 클래스가 동일한 형태로 재구현하는 행위를 메소드 오버라이딩이라고 하빈다.
위 코드에서 자식 클래스 houseDog는 부모 클래스 Dogsleep()을 오버라이딩합니다.


Method Overloading

public class HouseDog extends Dog {
    public void sleep() {
        System.out.println(this.name+" zzz in house");
    } 

    public void sleep(int hour) {
        System.out.println(this.name+" zzz in house for " + hour + " hours");
    } 

    public static void main(String[] args) {
        HouseDog houseDog = new HouseDog();
        houseDog.setName("happy");
        houseDog.sleep();     // sleep() 메소드 호출
        houseDog.sleep(3);   // sleep(int hour) 메소드 호출
    }
}

입력 항목이 다른, 동일한 이름을 가진 메소드를 만드는 것을 메소드 오버로딩이라고 합니다.
위의 코드와 같이 sleep() 메소드들을 호출하면

happy zzz in house
happy zzz in house for 3 hours

이와 같이 호출됩니다.


다중 상속

다중 상속은 클래스가 동시에 하나 이상의 클래스를 상속받는 것을 뜻합니다.
그러나 자바는 다중 상속을 지원하지 않습니다.

class C extends A, B {
    public void static main(String[] args) {
        C test = new C();
        test.msg();
    }
}

위와 같이 선언되었을 때 msg() 함수를 A 클래스와 B 클래스가 가지고 있다고 한다면 애매한 상황이 발생할 수 있다.


🎯 Constructor

메소드명이 클래스명과 동일하고 리턴 자료형이 없는 메소드를 생성자(Constructor)라고 합니다.


생성자의 규칙

  • 클래스명과 메소드명이 동일합니다.
  • 리턴 타입을 정의하지 않습니다.
public HouseDog(String name) {
    this.setName(name);
} 

default 생성자

public class Dog extends Animal {
    public Dog() {
    }
}

위와 같이 생성자의 입력 항목이 없고 생성자 내부에 아무 내용이 없는 생성자를 default 생성자라고 합니다.

만약 클래스에 생성자가 하나도 없다면 컴파일러는 자동으로 디폴트 생성자를 추가합니다.
그러나 생성자가 하나라도 존재한다면 디폴트 생성자는 추가되지 않습니다.


생성자 오버로딩

public class HouseDog extends Dog {
    public HouseDog(String name) {
        this.setName(name);
    }

    public HouseDog(int type) {
        if (type == 1) {
            this.setName("yorkshire");
        } else if (type == 2) {
            this.setName("bulldog");
        }
    }
 }

위의 클래스와 같이 입력 항목이 다른 생성자를 여러개 만든 것을 생성자 오버로딩이라고 합니다.

HouseDog happy = new HouseDog("happy");
HouseDog yorkshire = new HouseDog(1);

이렇게 두가지 방법으로 생성이 가능합니다.


🎯 Interface

동물원에 사육사가 있다.
사육사는 육식동물에게 먹이를 던져준다.
사육사는 호랑이가 들어오면 사과를 던져준다.
사육사는 사자가 들어오면 바나나를 던져준다.

이러한 로직을 인터페이스와 클래스를 통해 구현해보도록 하겠습니다.

// Animal.java
public class Animal {
    String name;

    public void setName(String name) {
        this.name = name;
    }
}

// Tiger.java
public class Tiger extends Animal {

}

// Lion.java
public class Lion extends Animal {

}

// ZooKeeper.java
public class ZooKeeper {
    public void feed(Tiger tiger) {
        System.out.println("feed apple");
    }

    public void feed(Lion lion) {
        System.out.println("feed banana");
    }

    public static void main(String[] args) {
        ZooKeeper zooKeeper = new ZooKeeper();
        Tiger tiger = new Tiger();
        Lion lion = new Lion();
        zooKeeper.feed(tiger);		// feed apple
        zooKeeper.feed(lion);		// feed banana
    }
}

TigerLionAnimal을 상속합니다.
그리고 사육사 ZooKeeper는 호랑이, 사자에 따라 다른 feed() 메서드를 호출합니다.

여기서 feed()메소드 오버로딩(Method Overloading)입니다.

만약 동물이 계속 추가된다면, feed() 메소드를 계속 추가해주어야합니다.



그렇다면 이 문제를 어떻게 해결할까요? 🤔

public interface Predator {

}

public class Tiger extends Animal implements Predator { ... }
public class Lion extends Animal implements Predator  { ... }

육식동물 Predator는 인터페이스로 만들어 TigerLion에 상속 받을 수 있습니다.

// ZooKeeper.java
public void feed(Predator predator) {
    System.out.println("feed apple");
}

그리고 ZooKeeperfeed() 메소드를 Predator로 포괄하도록 만들 수 있습니다.
이제 어떤 동물이 추가 되더라도 feed() 를 생성해줄 필요가 없습니다.

public interface Predator {
    public String getFood();
}

public class Tiger extends Animal implements Predator {
    public String getFood() {
        return "apple";
    }
}

public class Lion extends Animal implements Predator {
    public String getFood() {
        return "banana";
    }
}

그리고 육식 동물들마다 다른 출력을 나타내는 것을 구현하기 위해서 위와 같이 생성해줄 수 있습니다.

public class ZooKeeper {    
    public void feed(Predator predator) {
        System.out.println("feed "+predator.getFood());
    }
}

위 코드를 통해 ZooKeeprfeed() 어떤 객체가 들어오는지에 따라서 출력값을 달리할 수 있습니다.


ZooKeeper 클래스가 동물들 종류에 의존적인 클래스에서 동물들 종류와 상관없는 독립적 클래스가 되었다는 것이 인터페이스의 핵심입니다.


🎯 다형성 (Polymorphism)

public class Bouncer {
    public void barkAnimal(Animal animal) {
        if (animal instanceof Tiger) {
            System.out.println("어흥");
        } else if (animal instanceof Lion) {
            System.out.println("으르렁");
        }
    }

    public static void main(String[] args) {
        Tiger tiger = new Tiger();
        Lion lion = new Lion();

        Bouncer bouncer= new Bouncer();
        bouncer.barkAnimal(tiger);		// 어흥
        bouncer.barkAnimal(lion);		// 으르렁
    }
}

barkAnimal 메소드는 animal 객체가 Tiger인 경우 "어흥"을, Lion인 경우 "으르렁"을 출력합니다.

instanceof : 특정 객체가 특정 클래스의 객체인지 조사할 때 사용되는 자바의 내장 키워드입니다.


public interface Barkable {
    public void bark();
}

public class Tiger extends Animal implements Predator, Barkable {
    public String getFood() {
        return "apple";
    }

    public void bark() {
        System.out.println("어흥");
    }
}

public class Lion extends Animal implements Predator, Barkable {
    public String getFood() {
        return "banana";
    }

    public void bark() {
        System.out.println("으르렁");
    }
}

public void barkAnimal(Barkable animal) {
    animal.bark();
}

TigerLionBarkable 인터페이스를 상속받아 bark 메소드를 오버라이드합니다.
barkAnimal 함수가 인터페이스를 사용한 후 훨씬 간단하고 명확해진 것을 확인할 수 있습니다.


이렇게 하나의 객체가 여러개의 자료형 타입을 가질 수 있는 것을 객체 지향 세계에서는 다형성(Polymorphism)이라고 합니다.


🎯 Abstract class

추상 클래스는 인터페이스의 역할도 하면서 동시에 구현체도 가지고 있는 클래스라고 합니다.
쿠상클래스는 인터페이스로 대체하는 것이 보통 좋은 디자인이라고 말합니다.

public abstract class Predator extends Animal {
    public abstract String getFood();
}

Predator 인터페이스와 getFood 메소드를 abstract로 표기하였습니다.
abstract 메소드는 인터페이스의 메소드와 마찬가지로 몸통이 없습니다. abstract 클래스를 상속하는 클래스에서 메소드가 구현됩니다.


public class Tiger extends Predator implements Barkable {
    public String getFood() {
        return "apple";
    }

    public void bark() {
        System.out.println("어흥");
    }
}

public class Lion extends Predator implements Barkable {
    public String getFood() {
        return "banana";
    }

    public void bark() {
        System.out.println("으르렁");
    }
}

abstract 클래스는 extends 키워드를 사용해 상속합니다.

abstract 클래스에서 abstract로 선언된 메소드는 인터페이스 메소드와 마찬가지로 abstract 클래스를 상속하는 클래스에서 반드시 구현해야만 하는 메소드입니다.

따라서 TigerLion 클래스는 abstract 클래스인 Predator를 상속 받았기 때문에 getFood 메소드를 반드시 구현해야합니다.

public abstract class Predator extends Animal {
    public abstract String getFood();

    public boolean isPredator() {
        return true;
    }
}

abstract 클래스에는 abstract 메소드뿐만 아니라 실제 메소드도 추가가 가능합니다.


🛴 마무리

객체 지향은 이론은 쉽지만 설계하기가 참 까다로운 느낌이 드네요 😂
다음은 입출력으로 돌아오겠습니다!!


참고

0개의 댓글