추상클래스

추상클래스

추상클래스(abstract class)는 OOP 언어의 특징 중 하나로, 클래스가 무엇인가 하는 점과 그것이 어떻게 구현되는가 하는 지점의 경계에 있는 개념이다. 이 타입의 메소드는 실제 메소드 및 추상 메소드를 포함한다. 추상 클래스는 인스턴스화 될 수 없으며, 인스턴스를 만들고 싶다면 해당 클래스를 상속받는 서브 클래스를 만들어서 인스턴스화해야 한다.

실제 현실에서의 예를 들면 ‘자판기’를 생각할 수 있다. 우리는 자판기에 돈을 넣고 품목을 선택하면 해당 상품이 나온다. 여기서 중요한 것은 자판기가 어떤 일을 하는가에 대한 점이다. 우리는 자판기 내부 구조를 몰라도 자판기에는 돈을 넣고 선택해서 과자나 음료가 나온다는 것만 알면 된다. 이러한 작동 방식 혹은 실제 세계와 소통 하는 방식이 자판기를 규정한다고 하면, 자판기를 만드는 메이커 역시 자판기의 원형이라는 것이 존재하지 않더라도 자판기가 해야하는 일 (동전을 받고, 물건을 선택하고, 상품을 내놓는)을 하는 기계를 만들면 된다는 것을 알면 되는 것과 비슷하다. 이에 비해 자판기의 베이스 클래스가 있다면, 이를 변형하는 것으로 (상품의 품목을 바꾼다거나) 새로운 자판기를 만들 수도 있을 것이다. 이 때 전자는 추상 클래스에 해당하고 후자가 일반적인 서브 클래싱에 해당한다.

자바의 예

자바에서 추상 클래스는 다음과 같이 작성한다.


abstract class Canine { String name; String color; String gender; int age; /** constructor **/ Canine(String name, String color, int age, char mF) { this.name = name; this.color = color; this.age = age this.gender = (mF == 'M') ? "Male" : "Female"; } /** abstrct method **/ abstract String getBreed(); /** defined method **/ void printInfo() { System.out.println(name + " is " + ((age % 10 == 8) ? "an " : "a ") + age + " years old " + gender + getBreed() + " with a " + color + " coat."); } } class KleeKai extends Canine { KleeKai(String name, String color, int age, char mF) { super(name, color, age, mF); } String getBreed() { return "KleeKai" } }

Swift

Swift는 추상 클래스를 제공하지 않는다. 대신에 프로토콜이 이와 같은 역할을 할 수 있으며, 심지어 클래스가 아닌 구조체에서도 같은 문맥을 사용할 수 있다.

protocol Canine {
    var name: String { get }
    var color: String { get }
    var gender: String { get }
    var age: Int { get set }

    func getBreed() -> String
}

extension Canine {
    func printInfo() {
        print("\(name) is \(age % 10 == 8 ? "an" : "a") \(age) years old \(gender) \(getBreed()) with a \(color) coat.")
    }
}

struct KleeKai: Canine {
    let name: String
    let color: String
    let gender: String
    var age: Int

    init(name: String, color: String, gender: String, age: Int){
        self.name = name
        self.color = color
        self.gender = gender == "M" ? "Male" : "Female"
        self.age = age
    }

    func getBreed() -> String {
        return "KleeKai"
    }
}

(연습문제) 대소문자변환

연습문제 : 대소문자 변환하기

입력받은 문자열의 대소문자를 반전하여 출력하는 프로그램을 작성하시오.

입력받은 각 글자의 문자 코드가 ‘a’ … ‘z’ 사이에 있으면 소문자, ‘A’~’Z’ 사이에 있으면 대문자이다. 그리고 그 변환은 해당 코드값에 a - A를 더하거나 빼주면 된다.

먼저 대소문자의 범위를 찾아보자.

print("azAZ".utf8.map(Int.init))
//=> [97, 122, 65, 90]

즉 문자 코드가 97~122 구간에 있으면 소문자, 65~90 구간에 있으면 대문자이다. 소문자->대문자 변환은 32를 빼고, 반대의 변환은 32를 더한다.

문자 코드 값으로 다시 문자를 만드는 방법은 다음과 같다.

  1. 문자열은 [Character] 타입으로 만들 수 있다.
  2. 코드값으로 Character 인스턴스를 만들기 위해서는 Int 값을 UnicodeScalar 값으로 변환하면 된다.

즉, 다음과 같이 변경할 수 있다.

let arr = [97, 122, 65, 90]
let str = String(arr.map{ Character(UnicodeScalar($0)) })
print(str)
//=> "azAZ"

전체 코드는 다음과 같다.

if let s = readLine(), case let u = s.utf8.map(Int.init) {
    let h: Int -> Int = { n in
        switch n {
        case 97...122: return n - 32
        case 65...96: return n + 32
        default: return n
        }
    }
    let result = String(u.map{ Character(UnicodeScalar(h($0))) })
    print(result)
}

위의 switch 문은 간단히 삼항 연산자의 중첩으로 쓸 수 있다. 범위(Range<Int>)에 특정 값이 있는지는 패턴매칭으로 찾는데, 패턴매칭은 연산자 ~=를 쓰므로 다음과 같이 코드를 줄일 수 있다.

/* shorter version */
if let s = readLine(), case let u = s.utf8.map(Int.init) {
    let h: Int -> Int = { n in return 97...122 ~= n ? n - 32 : 65...96 ~= n ? n + 32 : n }
    let result = String(u.map{ Character(UnicodeScalar(h($0))) })
    print(result)
}

하스켈 풀이

하스켈의 경우, Data.Char 모듈에 isUpper, toUpper, toLower 함수가 정의되어 있으므로 이를 이용할 수 있다.

import Data.Char
main = interact (map f) where f c = if isUpper c then toLower c else toUpperc

interact 함수는 String -> String -> IO () 타입의 함수로 입력받은 문자열을 처리해주는 함수를 받아서 입력과 출력을 연결해주는 함수이다. 즉,

main = getLine >= (\xs -> putStrLn . (map f) $ xs)

를 줄인 것이다.