본문 바로가기

Java/생활코딩

자바(Java) 개념 정리 - 객체 지향 프로그래밍 #16

method

function

subroutine

procedural

 

다양한 프로그래밍 언어에서 메소드의 역할을 위와 같은 단어로 사용한다.

 

procedural programming (절차지향 프로그래밍) 은 JAVA에서는 method programming 으로 프로그래밍을 정리정돈하는 기법이다. 메소드를 이용해서 작은 부품을 만들고 이 것을 이용해서 큰 프로그램을 만드는 것이다. 많은 프로그래밍 언어가 이러한 패러다임을 따르고 있다. 대표적인 언어가 C이다. 

 

그런데 몇몇 컴퓨터 엔지니어는 메소드만으로 프로그램을 만드는 것을 불편함을 느꼈다. 그래서 서로 연관된 메소드와 변수들을 모아서 그룹핑 하고 정리정돈할 수 있는 수납상자 역할이 class이다. 클래스를 중심으로 프로그래밍 구조를 만들어가는 컴퓨터 프로그래밍 방법론이 객체 지향 프로그래밍(object oriented programming) 이러한 방법론을 언어차원에서 제공하는 언어들을 객체 지향 언어라고 한다.


남의 클래스, 남의 인스턴스

 

               System.out.println(Math.PI);
              System.out.println(Math.floor(1.8));
              System.out.println(Math.ceil(1.8));

출력
3.141592653589793
1.0
2.0

Math라는 클래스에 PI라는 변수가 있는 것이고 PI는 적당한 정밀도로 원주율을 나타내고 있다.

Math라는 클래스에 floor라는 메소드로 1.8의 값을 내림으로 출력하고 있다.

Math라는 클래스에 ceil라는 메소드로 1.8의 값을 올림으로 출력하고 있다.

              FileWriter f1 = new FileWriter("data.txt");

어떤 정보를 파일에 기록할 때 사용하는 클래스인 FileWriter

new를 붙이므로써 "data.txt"에다가 파일을 저장하겠다는 상태를 지닌 FileWriter의 복제본이다.

그러면 이 상태의 클래스를 FileWriter라는 데이터 타입의 f1에 넣어준다.

 

참고로 FileWriter는 자바의 기본 클래스가 아니기 때문에 main 클래스 최상단에 import해준다.

import java.io.FileWriter;
import java.io.IOException;
public class OthersOOP {
       public static void main(String[] args) throws IOException {
              
              System.out.println(Math.PI);
              System.out.println(Math.floor(1.8));
              System.out.println(Math.ceil(1.8));
              
              FileWriter f1 = new FileWriter("data.txt");
              f1.write("Hello");
              f1.write(" java");
              f1.close();
       }
}

또 하나는 파일이 없거나 이런 예외의 경우를 처리해주는 예외 처리 클래스를 넣어줘야 한다. 또한 main 메소드 뒤에 throws IOException을 작성해준다. 사실 에러뜨면 빨간줄 따라서 클릭 클릭하면 된다. 이후 인스턴스를 활용해서 내용을 작성한후 close(FileWriter 클래스는 마지막에 close를 해줘야 한다. 파일작업을 끝냈다는 의미이다.)까지 마치고 출력을 하면 txt파일이 작성된다.

 

Math는 클래스를 사용한 경우고,

FileWriter는 "data.txt"라는 파일을 작성해야한다는 상태를 임의로 부여한 복제본을 가지고 사용한 경우다.

 

Math 클래스의 PI, floor, ceil 등의 메소드는 필요할 때마다 사용하면 되면 1회용도이다.

FileWriter라는 클래스처럼 긴 맥락을 가지는 클래스는 클래스를 복제해서 인스턴스를 만들고 제어해야 한다. 

import java.io.FileWriter;
import java.io.IOException;
public class OthersOOP {
       public static void main(String[] args) throws IOException {
              //class : System, Math, FileWriter
              //instance : f1, f2
              
              System.out.println(Math.PI);
              System.out.println(Math.floor(1.8));
              System.out.println(Math.ceil(1.8));
              
              FileWriter f1 = new FileWriter("data.txt");
              f1.write("Hello");
              f1.write(" java");
              f1.close();

              FileWriter f2 = new FileWriter("data2.txt");
              f2.write("Hello");
              f2.write(" java2");
              f2.close();
       }
}

Class

 

자 다음과 같은

 

public class MyOOP {
       public static void main(String[] args) {
              // 1억줄
              printA();
              printA();
              printB();
              printB();
              // A
              printA();
              printA();
              printB();
              printB();
       }
       public static void printA() {
              System.out.println("----");
              System.out.println("A"); // 서로 연관된 로직이다.
              System.out.println("A");
       }
       public static void printB() {
              System.out.println("----");
              System.out.println("B");
              System.out.println("B");
       }
}

클래스가 있다고 하자. 이때 주석A 밑의 코드만 구분자를 "****"로 바꾸고 싶을 때 어떻게 해야할까?

public class MyOOP {
       public static void main(String[] args) {
              // 1억줄
              printA("----");
              printA("----");
              printB("----");
              printB("----");
              
              printA("****");
              printA("****");
              printB("****");
              printB("****");
       }
       public static void printA(String delimiter) {
              System.out.println(delimiter);
              System.out.println("A"); // 서로 연관된 로직이다.
              System.out.println("A");
       }
       public static void printB(String delimiter) {
              System.out.println(delimiter);
              System.out.println("B");
              System.out.println("B");
       }
}

이렇게 바꿔주면 된다. 아마 이고잉은 요거를 인스턴스로 바꾸는걸 원할 것 같다. 미리 바꿔보자.

 

 

메소드 안에서 정의된 변수는 그 메소드 안에서만 쓸 수 있다. 그것이 지역변수의 유효범위이다.

 

public class MyOOP {

       public static String delimiter = "";

       public static void printA() {
              System.out.println(delimiter);
              System.out.println("A");
              System.out.println("A");
       }
       public static void printB() {
              System.out.println(delimiter);
              System.out.println("B");
              System.out.println("B");
       }
       public static void main(String[] args) {
              // 1억줄
              delimiter = "----"
              printA();
              printA();
              printB();
              printB();

              delimiter = "****"              
              printA();
              printA();
              printB();
              printB();
       }

}

이렇게 작성해주면 그때 그때 delimiter라는 변수의 값을 정해줘서 각각 다른 값을 출력할 수 있다.

 

이제 이 코드를 정리하기 위해 class로 만들어준다.

class Print{
    public static String delimiter = "";
    public static void A() {
        System.out.println(delimiter);
        System.out.println("A");
        System.out.println("A");
    }
    public static void B() {
        System.out.println(delimiter);
        System.out.println("B");
        System.out.println("B");
    }
}
public class MyOOP {
    public static void main(String[] args) {
        Print.delimiter = "----";
        Print.A();
        Print.A();
        Print.B();
        Print.B();
         
        Print.delimiter = "****";
        Print.A();
        Print.A();
        Print.B();
        Print.B();
    }
}

훨씬 정리정돈이 잘 되어있다. 또한, 하나의 파일안에서 다양한 클래스를 만들면 클래스는 파일 단위로 저장이 된다. 한 java 파일 안에서 클래스 만들면 그렇게 된다. 또한 클래스가 부품으로만 존재하면 main은 사실상 필요없다.

 

다시 정리하면 이전에 우리가 자바파일 안에 클래스도 만들고 main 코드도 작성하느라 MyOPP 클래스가 복잡해졌다. 이를 분리하기위해 같은 프로젝트안에 Print라는 클래스를 따로 만들어주자.

 

class Print {
       public static String delimiter;
       public void a() {
              System.out.println(this.delimiter);
              System.out.println("A"); // 서로 연관된 로직이다.
              System.out.println("A");
       }
       public void b() {
              System.out.println(this.delimiter);
              System.out.println("B");
              System.out.println("B");
       }
}
public class MyOOP {
    public static void main(String[] args) {
        Print.delimiter = "----";
        Print.A();
        Print.A();
        Print.B();
        Print.B();
        Print.delimiter = "****";
        Print.A();
        Print.A();
        Print.B();
        Print.B();
    }
}

이렇게 나눠준다. 분리 성공 ☆ 

 

사실 분리하는 좋은 방법이 있다. class를 드래그 -> Refactor -> Move Type to New File -> preview를 통해서 변하는 내용을 확인 -> OK java, class 생성


instance

 

public class Print {
       public static String delimiter;
       public void a() {
              System.out.println(this.delimiter);
              System.out.println("A"); // 서로 연관된 로직이다.
              System.out.println("A");
       }
       public void b() {
              System.out.println(this.delimiter);
              System.out.println("B");
              System.out.println("B");
       }
}

이 클래스를 활용해서 다른 변수의 값을 주고 싶을 수 있다.

 

Print P1 = new Print ();

 

Print라는 데이터타입의 변수 P1은 Print() 클래스의 복제(인스턴스)이다.

 

Print()는 3개의 메소드를 가지고 있다.

 

1번은 delimiter = ""

2번은 a()

3번은 b()

 

이 때, public static String delimiter에서 static이 의미하는 것은 해당 클래스의 '소속'임을 의미한다. 인스턴스로 해당 메소드를 사용하려면 static을 빼줘야 한다.

 

빼주자.

 

public class Print {
       public String delimiter;
       public void a() {
              System.out.println(this.delimiter);
              System.out.println("A"); // 서로 연관된 로직이다.
              System.out.println("A");
       }
       public void b() {
              System.out.println(this.delimiter);
              System.out.println("B");
              System.out.println("B");
       }
}

 

이렇게 빼주고 main 클래스를 들어가보자.

 

클래스.메소드

p1.delimiter

p1.a()

p1.b()

 

이렇게 3가지를 활용할 수 있다.

 

delimiter는 인스턴스에 사용이 가능하도록 설정을 해두었으니 설정값을 변경해서 사용할 수 있다.

 

public class MyOOP {
    public static void main(String[] args) {
        Print p1 = new Print();
        p1.delimiter = "----";
        p1.a();
        p1.a();
        p1.b();
        p1.b();

        Print p2 = new Print();
        p2.delimiter = "****";
        p2.a();
        p2.a();
        p2.b();
        p2.b();
         
         
        p1.a();
        p2.a();
        p1.a();
        p2.a();
    }

코드가 훨씬 명료해졌다. 노가다를 줄여나가는 것이 훌륭한 코드이다. 유지보수에도 도움이 된다.


static

 

 

main 클래스 위에 간단하게 Foo 클래스를 만들어주자

 

Foo 클래스에 각각 클래스 변수, 인스턴스 변수를 만들어주자.

 

class Foo{
       public static String classVar = "I class var";
       
       public String instanceVar = "I instance var";
}

classVar는 static이 있는 클래스 변수이다.

 

instanceVar는 static이 없는 인스턴스를 위한 변수이다.

 

이후 아래의 main 클래스에서 앞서 만든 2가지 메소드를 활용해보자.

public class StaticApp {
       public static void main(String[] args) {
              
              System.out.println(Foo.classVar); // OK
              System.out.println(Foo.instanceVar); // Error
       }
}

classVar 변수는 다른 클래스에서도 클래스 변수에 대한 접근이 가능하다.

 

intanceVar는 인스턴스로 사용하기 위해 고안된 변수이기 때문에 클래스 변수로 사용할 수 없다.

 

다음은 클래스 메소드와 인스턴스 메소드를 만들어보자.

class Foo{
       public static void classMehtod() {
              System.out.println(classVar); // Ok
//            System.out.println(instanceVar); // Error
       }
       public void instanceMethod() {
              System.out.println(classVar); // Ok
              System.out.println(instanceVar); // Ok
       }
}

static이 들어간 클래스 메소드 안에서는 클래스 변수는 사용이 가능하지만 인스턴스 변수가 사용이 불가능하다.

 

static이 없는 인스턴스 메소드 안에서는 클래스 변수와 인스턴스 변수가 둘다 사용이 가능하다.

 

 

다음은 이 두 메소드를 호출해보자.

public class StaticApp {
       public static void main(String[] args) {
              Foo.classMehtod();
//            Foo.instanceMethod(); // Error

위와 같이 인스턴스 메소드는 클래스 메소드 접근 방식으로는 접근할 수 없다.

지금까지 한 내용을 정리하면 위와 같은 그림으로 나타낼 수 있다.

 

instance f1에는 실제 존재하는 값이 없고, classVar를 가리키고 있다. 하지만 instanceVar의 값은 복제가 될 뿐이다.

 

그래서 instance안에 있는 classVar의 값을 바꾸면 class Foo의 classVar가 바뀌고 역도 성립한다.

 

메소드도 마찬가지이다.

 

instance의 변수와 메소드는 독립되어 있고,

 

class의 변수와 메소드는 연결되어있다. 값을 바꾸면 모든 클래스의 값이 바뀐다.

 

 

그러면 이제 인스턴스 접근 방식이다.

public class StaticApp {
       public static void main(String[] args) {
              
              System.out.println(Foo.classVar);
//            System.out.println(Foo.instanceVar);
              
              Foo.classMehtod();
//            Foo.instanceMethod(); // Error
              
              Foo f1 = new Foo();
              Foo f2 = new Foo();
//            
              System.out.println(f1.classVar); // I class var
              System.out.println(f1.instanceVar); // I instance var
//            
              f1.classVar = "changed by f1";
           System.out.println(Foo.classVar); //changed by f1
              System.out.println(Foo.classVar); //changed by f1
//            
              f1.instanceVar = "changed by f1";
              System.out.println(f1.instanceVar); // changed by f1
              System.out.println(f2.instanceVar); // I instance var
//
       }
}

 

위와 같은 방식으로 나타난다. 그림과 대조하면서, 디버거를 사용하면서 이해하도록 노력하자.


 

생성자

              FileWriter f1 = new FileWriter("data.txt");
              f1.write("Hello");
              f1.write(" java");
              f1.close();

 

이렇게  클래스를 호출할 때, "data.txt"는 파일을 수정한다는 행위를 나타내는 FileWriter라는 클래스는 수정할 수 있는 파일을 필수적으로 지정해줘야 한다. 인스턴스를 만들 때, 파일을 지정할 수 있다면 파일을 지정하는 것을 까먹지 않는다.

 

이 인스턴스가 생성될 때, 꼭 처리해야하는 초기값, 꼭 실행되어야하는 어떠한 작업이 있을 때 생성자를 이용한다.

 

              Print p1 = new Print();
              p1.delimiter = "----";
              p1.a();
              p1.b();

만약에 다음과 같은 인스턴스를 생산할 때, 우리는 delimiter값을 설정해야 하지만 사용자가 이 작업을 까먹기가 쉽다. 우리는 인스턴스를 생성할 때 delimiter값을 지정하지 않으면 우리가 만든 클래스가 인스턴스화 하지 못하게 한다면 사용자가 실수할 수 있는 가능성을 원천 차단할 수 있다.

 

우리는 이 과정을 위해서 생성자(constructer)를 정의해줘야 한다.

              Print p2 = new Print("****");

이와 같은 형식으로 나타내고 싶을 때, 앞에 new 만 없다면 메소드를 호출하는 형식과 똑같다. 클래스는 생성자라고 하는 특수한 메소드를 구현할 수 있는 기능을 제공하고 그를 위한 중요한 작업이 초기화다.

class Print {
       public String delimiter;
    public Print() {
       
    }

Print 클래스에 다음과 같이 코드를 설정하면 우리가 Print() 클래스를 인스턴스화 할 때, 그와 같이 똑같이 생긴 메소드를 만들었다면 우리는 똑같은 모양을 호출하기로 약속되어 있기 때문에 그 클래스가 인스턴스화 할 때, 실행되어야 할 코드를 contructer 메소드 안에 정의하는 것을 통해서 초기화의 목적을 달성할 수 있다.

 

 

우리는  Print p2 = new Print("****"); 이와 같이 구분자를 인자로써 필수로 받길 원하고 있다. 그러기 때문에 프린트 클래스의 생산자 메소드를 같은 형식으로 변경해준다.

 

class Print {
       public String delimiter;
    public Print(String _delimiter) {
       delimiter = _delimiter;
    }

 

다음과 같이 바꿔주면 된다.  이것이 생성자이다. 생성자는 클래스와 같은 메소드 형식을 지정해주면 된다. 생성자는 static, return 데이터 타입을 지정하지 않는다.

 

만약 _delimiter이 delimiter로 작성을 했다면 

class Print {
       public String delimiter;
    public Print(String delimiter) {
       delimiter = delimiter;
    }

생성자의 매개변수가 delimiter가 되어서 인스턴스 변수가 아니라 생성자 메소드 매개변수로 적용이 되서 아무것도 실행이 안된다. 인스턴스 변수를 설정해줘야한다. 이런 경우에

class Print {
       public String delimiter;
    public Print(String delimiter) {
       this.delimiter = delimiter;
    }

this.를 붙여준다. this는 생성한 인스턴스를 가르킨다. this.delimiter는 인스턴스의 delimiter변수라고 잡아준다.

 

또한 아래의

       public void a() {
              System.out.println(this.delimiter);
              System.out.println("A"); 
              System.out.println("A");
       }
       public void b() {
              System.out.println(this.delimiter);
              System.out.println("B");
              System.out.println("B");
       }
}

 

인스턴스 변수에도 this.를 넣어주는 것이 정확하다. this는 해당 클래스가 인스턴스화되었을때 인스턴스임을 지정해주는 특수한 문자이다.


 

 

클래스와 인스턴스 활용

public class AccountingApp {
    // 공급가액
    public static double valueOfSupply = 10000.0;
    // 부가가치세율
    public static double vatRate = 0.1;
    public static double getVAT() {
        return valueOfSupply * vatRate;
    }
     
    public static double getTotal() {
        return valueOfSupply + getVAT();
    }
    public static void main(String[] args) {
        System.out.println("Value of supply : " + valueOfSupply);
        System.out.println("VAT : " + getVAT());
        System.out.println("Total : " + getTotal());
    }
}

이런 클래스가 있다고 하자. 하지만 우리가 공급가, 부가가치세 등의 계산의 종류가 많아지면 한 클래스안에서 관리가 매우 어려워 진다. 그래서 Accounting이라는 클래스를 만들고, 이 클래스에서 관리를 한다고 했을 때, 다음과 같이 나타낼 수 있다.

class Accounting{
    public static double valueOfSupply;
    public static double vatRate = 0.1;
    public static double getVAT() {
        return valueOfSupply * vatRate;
    }
    public static double getTotal() {
        return valueOfSupply + getVAT();
    }  
}
public class AccountingApp {
    // 공급가액
    public static void main(String[] args) {
        Accounting.valueOfSupply = 10000.0;
        System.out.println("Value of supply : " + Accounting.valueOfSupply);
        System.out.println("VAT : " + Accounting.getVAT());
        System.out.println("Total : " + Accounting.getTotal());
    }
}

사실 getTotal은 상당히 다양한 부문에서 나타날 수 있는 메소드 이름이다. 손님수의 합계를 나타날 때도, 재료 공급 수량을 나타낼 때도 getTotal이라는 메소드명을 사용하고 싶을 수 있다. 이 때, 각 계산을 클래스를 분리하면 위와 같이

 

Accounting.getTotal());

 

로 나타낼 수 있다. 이는 회계와 관련된 메소드임을 알려주는 용도로도 활용이 된다. 이는 getTotal이라는 메소드가 서로 다른 클래스임을 통해서 공존할 수 있다.

public class AccountingApp {
    public static void main(String[] args) {
        Accounting.valueOfSupply = 10000.0;
        System.out.println("Value of supply : " + Accounting.valueOfSupply);
        System.out.println("VAT : " + Accounting.getVAT());
        System.out.println("Total : " + Accounting.getTotal());
        
        Accounting.valueOfSupply = 20000.0;
        System.out.println("Value of supply : " + Accounting.valueOfSupply);
        System.out.println("VAT : " + Accounting.getVAT());
        System.out.println("Total : " + Accounting.getTotal());
    }
}

만약, AccountingApp의 내부적인 상태, 즉 valueOfSupply의 상태가 바뀌지 않는 경우에는 인스턴스가 아닌 클래스를 쓰면 된다.

만약, AccountingApp의 1차 작업(10000.0원일 때 과정)이 끝나고 2차 작업(valueOfSupply가 20000.0일 때 과정)만 실행할 것이면 인스턴스가 아닌 클래스를 쓰면 된다.

 

만약, valueOfSupply가 그때그때 변동이 될 때 변동이 되며 출력값도 그 때 그 때 달라질 때,

public class AccountingApp {
    public static void main(String[] args) {
        Accounting.valueOfSupply = 10000.0;
        System.out.println("Value of supply : " + Accounting.valueOfSupply);
        Accounting.valueOfSupply = 20000.0;
        System.out.println("Value of supply : " + Accounting.valueOfSupply);
        
        Accounting.valueOfSupply = 10000.0;
        System.out.println("VAT : " + Accounting.getVAT());
        Accounting.valueOfSupply = 20000.0;
        System.out.println("VAT : " + Accounting.getVAT());
        
        Accounting.valueOfSupply = 10000.0;
        System.out.println("Total : " + Accounting.getTotal());
        Accounting.valueOfSupply = 20000.0;
        System.out.println("Total : " + Accounting.getTotal());
    }
}

 

이렇게 표현된다.

 

우리가 valueOfSupply 상태가 2가지만 바뀔 때 위와 같이 표현할 수 있는데, 그 변수와 상태가 수십가지가 넘어갈 때 에러가 발생할 수 밖에 없다.

 

우리는 Accounting이라는 하나의 클래스를 여러 상태에 똑같은 방법을 돌려쓰기 때문에 이와 같은 문제가 발생한다.

 

이 때, 우리는 인스턴스를 사용한다.

public class AccountingApp {
    public static void main(String[] args) {
       
       Accounting a1 = new Accounting();
       a1.valueOfSupply = 10000.0;
       
       Accounting a2 = new Accounting();
       a2.valueOfSupply = 20000.0;
       
        System.out.println("Value of supply : " + a1.valueOfSupply);
        System.out.println("Value of supply : " + a2.valueOfSupply);
       
        System.out.println("VAT : " + a1.getVAT());
        System.out.println("VAT : " + a2.getVAT());
        
        System.out.println("Total : " + a1.getTotal());
        System.out.println("Total : " + a2.getTotal());
    }

이렇게 동작하게 하려면 우리는 a1의 valueOfSupply는 인스턴스임으로 Accounting 메소드의 static을 빼야 한다.

class Accounting{
    public double valueOfSupply;
    public static double vatRate = 0.1;
    public  double getVAT() {
        return valueOfSupply * vatRate;
    }
    public  double getTotal() {
        return valueOfSupply + getVAT();
    }  
}

valueOfSupply는 인스턴스가 되므로 static을 빼줘야 한다. static은 클래스 소속이기 때문이다. 마찬가지로

 

getVate 또한 인스턴스가 포함이 되어있기 때문에 static 제거

getTotal 또한 인스턴스가 포함이 되어있기 때문에 static 제거

 

해줘야 한다.

 

이렇게 하면 a1와 a2는 독립된 내부상태를 포함하고 있기 때문에 호출해주기 쉽다. 부가가치세 는 어떤 경우에도 변동이 되지 않기 때문에 인스턴스가 아닌 class의 소속인 static으로 내주면 좋다. 이러면 vatRate만 바꾸면 모든 클래스의 vatRate가 변경되기 때문에 편리할수도 있다.

 

이렇게 클래스화, 인스턴스화 할 때, 인스턴스가 내부적으로 생성될 때 valueOfSupply 값을 인스턴스할 때 강제하고 싶을 때(생성자, 초기화) 인자를 매개변수로 전달하고 싶을 것이다.

 

이 떄,

class Accounting{
    public double valueOfSupply;
    public static double vatRate = 0.1;
    public Accounting(double valueOfSupply) {
       this.valueOfSupply = valueOfSupply;
    }
    public  double getVAT() {
        return valueOfSupply * vatRate;
    }
    public  double getTotal() {
        return valueOfSupply + getVAT();
    }  
}

이렇게 

    public Accounting(double valueOfSupply) {
       this.valueOfSupply = valueOfSupply;
    }

이 놈을 입력해주면

public class AccountingApp {
    public static void main(String[] args) {
       
       Accounting a1 = new Accounting(10000.0);
       
       Accounting a2 = new Accounting(20000.0);
       
        System.out.println("Value of supply : " + a1.valueOfSupply);
        System.out.println("Value of supply : " + a2.valueOfSupply);
       
        System.out.println("VAT : " + a1.getVAT());
        System.out.println("VAT : " + a2.getVAT());
        
        System.out.println("Total : " + a1.getTotal());
        System.out.println("Total : " + a2.getTotal());
    }
}

코드가 한결 정리정돈 될 뿐만 아니라, 필수적으로 바꿔야하는 매개변수를 알려준다.


우리는 객체지향없이도 자바를 할 수 있다.

 

하지만 중요한건 실습이다. 우리가 클래스와 인스턴스에 익숙해지고 나서는

 

상속(Inheritance)

 

우리가 클래스를 직접 수정할 수 없거나 클래스를 수정하기 싫을 때, 클래스를 그대로 카피해서 수정한다.

 

이러한 방식은 이렇게 카피한 모든 코드를 하나하나 다 수정해야한다. 굉장히 비효율적이고 어렵다.

 

이를 위해 상속기능이 만들어졌다.

상속(inheritance)의 예

이러면 Child라는 클래스는 method1을 구현하지 못했지만 Parent에게 상속을 받았기 때문에 method1을 사용할 수 있다. 또한 parent의 method1을 바꾸면 상속받은 모든 클래스의 method1이 바뀌게 되는 결과를 갖는다.

 

 

인터페이스(Interface)

 

인터페이스에는 내용이 없다.

 

인터페이스에는 메소드의 이름, 파라미터, 리턴 값의 형식은 적지만 실제 내용은 적지 않는다.

인터페이스(interface)의 예

 

그래서 위와 같이 implements를 적어준다. concrete1이라는 클래스는 Contract에 적혀있는 메소드 형식을 구현했다, 구현해야한다는 것을 나타낸다.

 

만약 Concreat1이 형식을 지키지 않은 채로 컴파일을 시도하면 실행되지 않는다. 왜냐, 규칙을 어겼으니깐. 무슨 규칙? 인터페이스에 정의된 메소드들의 규칙.

 

 

 

Concreat1이 Contract의 형식을 구체적으로 구현해야 하는 책임을 갖게 된다.

 

Concreat2도 Contract의 형식을 구체적으로 구현해야 하는 책임을 갖게 된다.

 

이 두 클래스는 서로 다른 클래스이지만, Contract를 implement하고 있다면 Contract에 양속되어있고 정의되어있는 멤버들을 구현하고 있다는 것을 알 수 있다. 일종의 계약 같은 개념이다.

 

패키지(Package)

 

만약 같은 이름의 Class Foo가 있다면 안된다. 같은 이름의 Class Foo가 존재하기 위해서는 서로 다른 패키지에 담아야 한다.

 

또한 클래스가 많아지면 패키지로 정리해야한다. 패키지보다 포괄적인 디렉토리 개념은 없다.