본문 바로가기

Personal Posting/Flutter

Dart 내용 정리

 

  • Dart 변수는 기본적으로 Camel Case를 따른다.

  • var vs dynamic
    - var로 선언 후 값이 처음 담기면 이 때 지정된 타입으로 고정된다.
    - 즉, var tmp1 = 'This is Var'; 라고 적는 순간 tmp1 = 100;은 불가능 하단 소리다.
    - 하지만 dynamic은 이러한 처리가 가능하다.
    - 다만 var의 경우도 만약 var tmp1; 식으로 선언하게 되면 이후 tmp1 = 'This is Var'; tmp1 = 100; 처리가 가능하다.

  • final vs const
    - final 또는 const 키워드로 선언된 값은 변경이 불가능하다.
    - 다만 final은 런타임에 값을 지정하는 것이 허용이 된다.
    - 예를 들어, 현재 시각을 변수에 담는다고 할 때 const는 이것이 불가능하다.

  • for Loop
    - 일반적인 for loop 외에 for in 도 많이 사용한다.
    - List<int> integersList = [ 1,3,5,7,9 ];
       for (int i in integersList) { print(i); }

  • Named parameter로 Optional parameter 가진 함수 선언 및 사용
    - double linearExp (double a, double b, { double x }) { return a*b + x; }
       void main() { print( linearExp( 1, 2, x: 10 ); }

  • private 변수는 _ 를 넣는다. 참고로 dart에서는 파일단위로 체크를 하기 때문에 하나의 파일에 작성할 경우 접근이 가능하다.

  • getter, setter 사용과 Custom Function 사용의 차이
    - Dart에서 get 및 set 키워드를 사용하는 것과 직접 함수를 작성하는 것은 기능적으로 유사하다. 그러나 get, set 키워드를 사용하는 것이 일반적으로 Dart에서의 속성 즉, property 액세스를 더 자연스럽게 만들 수 있다.
    class Idol {
    	int _id;
        
        // using get + set
        int get id {
        	return _id;
        }
        set id (int value) {
        	_id = value;
        }
        
        // using custom func
        int getId() {
        	return _id;
        }
        void setId (int value) {
        	_id = value;
        }
    }
    
    void main() {
    	Idol myIdol = Idol();
        
        // usint get + set
        myIdol.id = 100;
        print(myIdol.id);
        
        // using custom func
        myIdol.setId(200);
        print(myIdol.getId());
    }​

    위의 코드에서 get 및 set 키워드를 사용한 경우와 사용하지 않은 경우의 기능은 동일하지만 일반적으로 Dart 코드에서는 속성에 get 및 set을 사용하여 속성 액세스를 더 자연스럽게 만들어 주는 것이 권장된다.

  • 클래스 간의 상속
    - Dart에서는 클래스 간의 상속을 extends 키워드를 사용하여 표현한다. 클래스 A가 클래스 B를 상속할 때, A는 B의 멤버(필드와 메서드)를 상속하게 된다.
    class Animal {
      void makeSound() {
        print('Some generic sound');
      }
    }
    
    class Dog extends Animal {
      @override
      void makeSound() {
        print('Bark!');
      }
    
      void fetch() {
        print('Fetching...');
      }
    }
    
    void main() {
      Dog myDog = Dog();
      myDog.makeSound(); // Output: Bark!
      myDog.fetch();     // Output: Fetching...
    }​

    예시에서 Dog 클래스는 Animal 클래스를 상속하고 있다. Dog 클래스는 makeSound 메서드를 오버라이드하여 자체적인 구현을 제공하고, 추가로 fetch 메서드를 가지고 있다.  상속의 주요 이점은 코드의 재사용과 확장성에 있는데, Animal 클래스의 특성을 Dog 클래스에서 상속함으로써 Dog 클래스는 Animal 클래스의 기능을 사용하면서 추가적으로 자체적인 기능을 확장할 수 있다.

    이에 비해 "인터페이스 구현"은 extends 대신 implements 키워드를 사용하며, 클래스가 특정 인터페이스의 모든 메서드와 필드를 구현해야 한다. Dart에서는 extends와 implements 모두 클래스 간의 계층 구조를 형성하며, 선택은 상황에 따라 다르다. Dart는 명시적으로 interface 키워드를 제공하지 않는 대신, 모든 클래스는 암시적으로 인터페이스로 동작한다.

    아래는 Dart에서 interface를 사용하는 예시이다.
    // 인터페이스 선언
    abstract class Shape {
      double area();
      double perimeter();
    }
    
    // 인터페이스를 구현한 클래스
    class Circle implements Shape {
      double radius;
    
      Circle(this.radius);
    
      @override
      double area() {
        return 3.14 * radius * radius;
      }
    
      @override
      double perimeter() {
        return 2 * 3.14 * radius;
      }
    }
    
    // 다른 인터페이스를 구현한 클래스
    class Rectangle implements Shape {
      double width;
      double height;
    
      Rectangle(this.width, this.height);
    
      @override
      double area() {
        return width * height;
      }
    
      @override
      double perimeter() {
        return 2 * (width + height);
      }
    }

    위의 코드에서 Shape는 인터페이스로 간주된다. Circle 및 Rectangle 클래스는 Shape 인터페이스를 구현하고 있다. 클래스가 특정 인터페이스를 구현하려면 앞서 언급했듯 implements 키워드를 사용한다.
    void main() {
      Circle circle = Circle(5);
      print('Circle Area: ${circle.area()}');
      print('Circle Perimeter: ${circle.perimeter()}');
    
      Rectangle rectangle = Rectangle(4, 6);
      print('Rectangle Area: ${rectangle.area()}');
      print('Rectangle Perimeter: ${rectangle.perimeter()}');
    }

    Dart에서는 클래스가 여러 인터페이스를 구현할 수 있다. 클래스는 인터페이스를 구현하면서 동시에 다른 클래스를 상속할 수도 있다. 이러한 유연성은 Dart에서 객체 지향 프로그래밍을 할 때 다양한 디자인 패턴을 활용할 수 있도록 한다.

  • implements와 extends의 차이점
    - implements와 extends의 주요 차이점은 클래스가 어떤 것을 상속하거나 구현하는지에 있다.

    1) implements:
      a) 클래스가 인터페이스를 implements 키워드를 사용하여 구현할 때, 해당 클래스는 인터페이스에서 선언된 모든 메서드와 필드를 구현해야 한다.
      b) 인터페이스를 implements하는 클래스는 인터페이스에서 선언된 메서드와 필드를 갖고 있어야 하지만, 기본 동작이 구현되어 있는 것은 아니다.
      c) 클래스는 여러 인터페이스를 implements할 수 있다.
    abstract class Shape {
      double area();
    }
    
    class Circle implements Shape {
      double radius;
    
      Circle(this.radius);
    
      @override
      double area() {
        return 3.14 * radius * radius;
      }
    }
     
    2) extends:
      a) 클래스가 다른 클래스를 extends 키워드를 사용하여 상속할 때, 해당 클래스는 상위 클래스의 모든 멤버를 상속받는다.
      b) 하위 클래스는 상위 클래스에서 이미 구현되어 있는 메서드를 사용할 수 있으며, 필요한 경우 오버라이드할 수 있다.
    class Animal {
      void makeSound() {
        print('Some generic sound');
      }
    }
    
    class Dog extends Animal {
      @override
      void makeSound() {
        print('Bark!');
      }
    }

    - 주로 extends는 클래스 간의 일반적인 상속 관계를 나타낸다. 반면에 implements는 클래스가 특정 인터페이스를 따르도록 하는데 사용되며, 클래스가 특정 기능을 제공하기 위해 필요한 인터페이스를 구현하도록 하는 경우에 사용된다.

  • super()에 대하여
    - super 키워드는 Dart에서 부모 클래스의 멤버에 접근하는 데 사용된다. 주로 하위 클래스에서 상위 클래스의 생성자, 필드 또는 메서드를 호출하는 데 사용된다.

    - 상위 클래스의 생성자 호출
    class Animal {
      String name;
    
      Animal(String name) {
        this.name = name;
        print('Animal constructor');
      }
    }
    
    class Dog extends Animal {
      Dog(String name) : super(name) {
        print('Dog constructor');
      }
    }
    여기서 Dog 클래스는 Animal 클래스를 상속하고 있다. Dog 클래스의 생성자에서 super(name)은 Animal 클래스의 생성자를 호출하며, 이를 통해 name 필드를 초기화한다.

    - 부모 클래스의 메서드 호출
    class Animal {
      void makeSound() {
        print('Some generic sound');
      }
    }
    
    class Dog extends Animal {
      @override
      void makeSound() {
        super.makeSound(); // 상위 클래스의 메서드 호출
        print('Bark!');
      }
    }
    여기서 super.makeSound()는 Animal 클래스의 makeSound 메서드를 호출한다. 하위 클래스에서 재정의한 메서드 내에서 부모 클래스의 동일한 메서드를 호출하려면 super를 사용한다.

    - super 키워드는 클래스 계층 구조에서 현재 클래스가 상속한 부모 클래스의 멤버에 접근하는 일반적인 방법을 제공한다.

  • Dart의 static키워드
    - Dart의 static 키워드는 특정 멤버(필드 또는 메서드)가 클래스 자체에 속하도록 지정하는 데 사용된다. 이는 해당 멤버가 클래스의 인스턴스에 속하는 것이 아니라 클래스 자체에 속한다는 의미이다.

    - 정적 필드(Static Fields)
    class Example {
      static int staticField = 42;
      int instanceField = 10;
    
      static void staticMethod() {
        print('Static method');
      }
    
      void instanceMethod() {
        print('Instance method');
      }
    }
    여기서 staticField는 클래스 Example의 정적 필드로, instanceField는 인스턴스 필드이다. 정적 필드는 클래스의 인스턴스 없이도 클래스 이름으로 직접 접근할 수 있다.

    - 정적 메서드 (Static Methods)
    class Example {
      static void staticMethod() {
        print('Static method');
      }
    
      void instanceMethod() {
        print('Instance method');
      }
    }
    staticMethod는 클래스의 정적 메서드로, 이 역시 클래스 이름을 통해 직접 호출할 수 있다. 정적 메서드는 클래스의 인스턴스 없이 호출된다.

    - 정적 멤버 사용 예시
    void main() {
      print(Example.staticField); // 출력: 42
      Example.staticMethod();     // 출력: Static method
    
      Example instance = Example();
      print(instance.instanceField);  // 출력: 10
      instance.instanceMethod();      // 출력: Instance method
    }
    위의 예시에서 staticField 및 staticMethod은 클래스 이름을 통해 직접 접근하고 호출할 수 있으며, instanceField 및 instanceMethod는 클래스의 인스턴스를 생성하여 사용한다.

    - 정적 멤버는 클래스와 연관되어 있지만 인스턴스에 속하지 않기 때문에, 인스턴스를 생성하지 않고도 사용할 수 있다. 이러한 특성은 유틸리티 클래스, 상수, 팩토리 메서드 등을 구현할 때 유용하다.

  • fold()
    - fold는 Dart에서 컬렉션을 탐색하고 각 요소에 대해 누적된 결과를 계산하는 메서드다. fold는 초기값과 결합 함수를 사용하여 요소를 순회하면서 결과를 누적한다.
    fold 메서드의 시그니처는 다음과 같다:
    E fold<E>(E initialValue, E combine(E previousValue, T element));
    // E: 누적된 결과의 타입
    // initialValue: 누적을 시작할 초기값
    // combine: 누적된 결과와 각 요소를 결합하는 함수
    void main() {
      List<int> numbers = [1, 2, 3, 4, 5];
    
      // 예시: 숫자의 합을 계산
      int sum = numbers.fold(0, (previousValue, element) => previousValue + element);
    
      print('Sum: $sum'); // Output: Sum: 15
    }
    위의 예시에서 fold를 사용하여 numbers 리스트의 숫자를 합산하고 있다. 초기값은 0이며, combine 함수는 각 요소를 이전까지의 누적된 결과에 더하는 동작을 수행한다.

    아래 예시는 문자열 리스트를 합치는 코드이다:
    void main() {
      List<String> words = ['Hello', ' ', 'World', '!'];
    
      // 예시: 문자열을 합치기
      String result = words.fold('', (previousValue, element) => previousValue + element);
    
      print('Result: $result'); // Output: Result: Hello World!
    }
    여기서도 fold를 사용하여 문자열 리스트를 누적하고, 각 문자열을 이전까지의 누적된 결과에 연결하는 동작을 수행하고 있다.

  • reduce()
    - reduce 메서드는 Dart에서 컬렉션을 탐색하고 각 요소를 조합하여 하나의 결과값을 계산하는 데 사용된다. reduce는 초기값을 사용하지 않으며, 각 요소를 차례대로 조합하여 최종 결과를 생성한다.
    reduce 메서드의 시그니처는 다음과 같다:
    T reduce<T>(T combine(T previousValue, T element));
    // T: 최종 결과의 타입
    // combine: 각 요소를 조합하여 최종 결과를 계산하는 함수
    void main() {
      List<int> numbers = [1, 2, 3, 4, 5];
    
      // 예시: 숫자의 곱셈을 계산
      int product = numbers.reduce((previousValue, element) => previousValue * element);
    
      print('Product: $product'); // Output: Product: 120
    }
    위의 예시에서 reduce를 사용하여 numbers 리스트의 숫자를 곱셈하여 최종 결과를 계산하고 있다. 초기값이 없으며, combine 함수는 각 요소를 이전까지의 누적된 결과에 곱하는 동작을 수행한다. 이번에도 아까와 같이 문자열 리스트의 길이를 합산하는 예시코드를 구현해보자.
    void main() {
      List<String> words = ['Hello', ' ', 'World', '!'];
    
      // 예시: 문자열 길이를 합산
      int totalLength = words.reduce((previousValue, element) => previousValue + element.length);
    
      print('Total Length: $totalLength'); // Output: Total Length: 12
    }
    여기서도 reduce를 사용하여 문자열 리스트의 각 문자열 길이를 누적하고, 각 길이를 이전까지의 누적된 결과에 더하는 동작을 수행하고 있다.