상세 컨텐츠

본문 제목

Dart - Class #2

Dart

by techbard 2024. 1. 4. 09:10

본문

반응형
// Constructor In Abstract Class
//
// Key Points To Remember
//
// You can’t create an object of an abstract class.
// It can have both abstract and non-abstract methods.
// It is used to define the behavior of a class that other classes can inherit.
// Abstract method only has a signature and no implementation.

// Note: Most of the time, abstract class is used instead of concrete class to declare an interface.

abstract class Bank {
  String name;
  double rate;

  Bank(this.name, this.rate);

  // abstract method
  interest();

  // non-abstract method: it have an implementation.
  show() {
    print('Bank name: $name');
  }
}

class JPM extends Bank {
  JPM(String name, double rate) : super(name, rate);

  @override
  interest() {
    print('The rate of interest of $name is $rate');
  }
}

class BOA extends Bank {
  BOA(String name, double rate) : super(name, rate);

  @override
  interest() {
    print('The rate of interest of $name is $rate');
  }
}

void main(List<String> args) {
  JPM jpm = JPM('JPM', 8.4);
  jpm.show();
  jpm.interest();

  BOA boa = BOA('BOA', 7.3);
  boa.show();
  boa.interest();
}

// 결과
Bank name: JPM
The rate of interest of JPM is 8.4
Bank name: BOA
The rate of interest of BOA is 7.3

 

// Declaring Interface In Dart

// In dart there is no keyword interface
// but you can use class or abstract class to declare an interface.
// All classes implicitly define an interface.
// Mostly abstract class is used to declare an interface.

// Multiple Inheritance In Dart
//
// Multiple inheritance means a class can inherit from more than one class.
// In dart, you can’t inherit from more than one class.
// But you can implement multiple interfaces in a class.

// 여러 인터페이스를 구현할 때 추상 메서드 이름 중복이 되면
// 어떻게 되나?
// -> 결론은 관계 없다. 구현부는 구상 클래스에 있기 때문에
// 인터페이스에 추상 메서드 이름 중복이 되어도 무관계
// -> 이래서 Dart에서 다중 클래스 상속을 금지하는 것으로 보인다.

// Difference Between Extends & Implements
// [extends]
// Used to inherit a class in another class.
// Gives complete method definition to sub-class.
// Only one class can be extended.
// It is optional to override the methods.
// Constructors of the superclass is called before the sub-class constructor.
// The super keyword is used to access the members of the superclass.
// Sub-class need not to override the fields of the superclass.
// [implements]
// Used to inherit a class as an interface in another class.
// Gives abstract method definition to sub-class.
// Multiple classes can be implemented.
// Concrete class must override the methods of an interface.
// Constructors of the superclass is not called before the sub-class constructor.
// Interface members can’t be accessed using the super keyword.
// Subclass must override the fields of the interface.

abstract class Area1 {
  area();
}

abstract class Area2 {
  area();
  anotherArea();
}

class Box implements Area1, Area2 {
  @override
  area() {
    return 1;
  }

  @override
  anotherArea() {
    return 2;
  }
}

void main(List<String> args) {
  Box b = Box();
  print(b.area());
  print(b.anotherArea());
}

// 결과
1
2

 

// Rules For Mixin
//
// Mixin can’t be instantiated. You can’t create object of mixin.
// Use the mixin to share the code between multiple classes.
// Mixin has no constructor and cannot be extended.
// It is possible to use multiple mixins in a class.

// mixin ElectricVC {
mixin ElectricVC {
  evCan() {
    print("Ev works.");
  }
}

mixin PetrolVC {
  ptCan() {
    print("Pt works.");
  }
}

// with is used to apply the mixin to the class
class Car with ElectricVC, PetrolVC {
  // here we have access of electricVariant() and petrolVariant() methods
}

void main(List<String> args) {
  Car c = Car();
  c.evCan();
  c.ptCan();
}

// 결과
Ev works.
Pt works.

 

class Tuple {
  // (제너릭스를 적용할 필요성이 생기는 예제)
  // Generics를 사용하면 extends 를 통해서 받을 타입을 제한할 수 있다.
  final int? _a;
  final int? _b;
  final int? _c;
  Tuple(this._a, this._b, this._c);
  Tuple.fromList(List<int> list) // indexing trick
      : _a = list.asMap().containsKey(0) ? list[0] : 0,
        _b = list.asMap().containsKey(1) ? list[1] : 0,
        _c = list.asMap().containsKey(2) ? list[2] : 0;

  int? get first => _a;
  int? get second => _b;
  int? get third => _c;

  Tuple operator +(Tuple t) => Tuple(_a! + t._a!, _b! + t._b!, _c! + t._c!);
  Tuple operator -(Tuple t) => Tuple(_a! - t._a!, _b! - t._b!, _c! - t._c!);

  @override
  String toString() => 'Tuple(first: $first, second: $second, third: $third)';
}

void main(List<String> args) {
  Tuple t1 = Tuple(1, 2, 3);
  Tuple t2 = Tuple.fromList([4, 5, 6]);
  Tuple t3 = Tuple.fromList([7]);
  Tuple t1t2 = t1 + t2;

  print('tuple1: $t1');
  print('tuple2: $t2');
  print('tuple3: $t3');
  print('t1 + t2: $t1t2');
}

// 결과
tuple1: Tuple(first: 1, second: 2, third: 3)
tuple2: Tuple(first: 4, second: 5, third: 6)
tuple3: Tuple(first: 7, second: 0, third: 0)
t1 + t2: Tuple(first: 5, second: 7, third: 9)

 

void main(List<String> args) {
  var stackInt = Stack<int>();
  var stackString = Stack<String>();

  stackInt.push(1);
  stackInt.push(2);
  stackInt.push(3);
  if (stackInt.canPop) {
    stackInt.pop();
  }

  print(stackInt.peak); // should print 2
}

class Stack<T> {
  final List<T> _stack = [];

  T pop() {
    final T last = _stack.last;
    _stack.removeLast();
    return last;
  }

  void push(T item) => _stack.add(item);
  T get peak => _stack.last;
  int get length => _stack.length;
  bool get canPop => _stack.isNotEmpty;
}

// 결과
2

 

// When & Why you should use generic types?
//
// 1. The When: you need to implement data structure of your own
// The Why: you won't have to reimplement the same class for each type
// 2. The When: Some portions of your code are kind of repetitive
// The Why: you will avoid code duplicates

void main(List<String> args) {
  var list = [1, 2, 3, 4, 5];
  print(Utils.getItem(list, 0));

  print("==============================");

  var anoList = ['a', 'b', 'c'];
  print(GenUtils.getItem(anoList, 0));
}

class Utils {
  static int? getItem(List<int> list, int index) => list.asMap().containsKey(index) ? list[index] : null;
}

class GenUtils {
  // the beauty of Generic method.
  static T? getItem<T>(List<T> list, int index) => list.asMap().containsKey(index) ? list[index] : null;
}

// 결과
1
==============================
a

 

// Null safety is a feature in the Dart programming language
// that helps developers to avoid null errors.
// This feature is called Sound Null Safety in dart.
// This allows developers to catch null errors at edit time.
//
// Note: Null safety avoids null errors, runtime bugs,
// vulnerabilities, and system crashes which are difficult to find and fix.
//
// Note: Common cause of errors in programming generally
// comes from not correctly handling null values.
//
// With dart sound null Safety, you cannot provide
// a null value by default.
// If you are 100% sure to use it, then you can use
// ? operator after the type declaration.
//
// Important Point In Dart Null Safety
//
// Null means no value.
// Common error in programming is caused due to null.
// Dart 2.12 introduced sound null Safety to solve null problems.
// Non-nullable type is confirmed never to be null.

class Person {
  String? name;
  Person(this.name);
}

class Profile {
  String? name;
  String? bio;

  Profile(this.name, this.bio);

  @override
  String toString() => '''Name: ${name ?? "Unknown"}, Bio: ${bio ?? "None provided"}''';
}

void printString(String? str) {
  print(str);
}

void main(List<String> args) {
  String? name;

  // give an erro // print(name);

  int? age; // define a nullable type variable
  print(age);

  // You can use if statement to check whether the variable is null or not.
  String? address;
  if (address == null) {
    print('address is null');
  }

  // Using ?? operator to assign a default value
  String UserAddress = address ?? 'Seoul';
  print(UserAddress);

  // Define List Of Nullable Items
  //
  // list of nullable ints
  List<int?> nums = [1, 2, 3, null];
  print(nums);

  // Define Function With Nullable Parameter
  //
  // If you are 100% sure, then you can use ?
  // for the type declaration.
  // In this example, the function printAddress has
  // a parameter address, which is a String? type.
  // You can pass both null and string values to this function.

  printString(null); // works

  // Null Safety In Dart Classes
  //
  Person p1 = Person(null); // works

  // Working With Nullable Class Properties
  Profile profile1 = Profile("John", "Software engineer and avid reader");
  Profile profile2 = Profile(null, null);

  print("==============================");

  print(profile1);
  print(profile2);
}

// 결과
null
address is null
Seoul
[1, 2, 3, null]
null
==============================
Name: John, Bio: Software engineer and avid reader
Name: Unknown, Bio: None provided

 

import 'dart:math';

class DataProvider {
  static String? get _stringornull => Random().nextBool() ? "Hello" : null;

  static void compareStrNull() {
    String? ret = _stringornull;

    if (ret is String) {
      print("The length of value is ${ret.length}");
    } else {
      print("The value is not string.");
    }
  }
}

void main(List<String> args) {
  DataProvider.compareStrNull();
}

// 결과
The length of value is 5

(The value is not string.)

 

void main(List<String> args) {
  // Map(String, Person)
  final monAndDad = {'mom': Person(), 'dad': Person()};
  // Map(String, CanSwim) -> Map supports genrics types.
  final brotherAndSisterAndMyFish = {
    'brother': Person(),
    'sister': Person(),
    'fishy': Fish()
  };

  final allValues = [monAndDad, brotherAndSisterAndMyFish];
  describe(allValues);
}

// 아래 둘다 가능하다. 취향에 따라 선택...
typedef SwimmingThings<T extends CanSwim> = Map<String, T>;
// typedef SwimmingThings = Map<String, CanSwim>;

void describe(Iterable<SwimmingThings> values) {
  for (var map in values) {
    for (var keyAndValue in map.entries) {
      keyAndValue.value.swim();
    }
  }
}

mixin CanSwim {
  void swim();
}

class Person with CanSwim {
  Person();
  @override
  swim() => print('person is swimming...');
}

class Fish with CanSwim {
  Fish();
  @override
  swim() => print('fish is swimming...');
}

// 결과
person is swimming...
person is swimming...
person is swimming...
person is swimming...
fish is swimming...

 

void main(List<String> args) {
  final JSON<String> json = {'name': 'John', 'age': '30'};
  print(json);

  final THING<String, int> th = {'first': 100, 'second': 101};
  print(th);
}

// constrain to one type
typedef JSON<T> = Map<String, T>;

// constrain to K, V
typedef THING<T, V> = Map<T, V>;

// 결과
{name: John, age: 30}
{first: 100, second: 101}

 

void main(List<String> args) {
  var person = Person(height: 1.7);
  var dog = Dog(height: 1);

  print(person.height);
  print(dog.height);
}

mixin HasHeight<T extends num> {
  T get height;
}

typedef HasIntHeight = HasHeight<int>;
typedef HasDoubleHeight = HasHeight<double>;

class Person with HasDoubleHeight {
  @override
  final double height;
  Person({required this.height});
}

class Dog with HasIntHeight {
  @override
  final int height;
  Dog({required this.height});
}

// 결과
1.7
1

 

void main(List<String> args) {
  var t1 = Tuple(1, 20);
  final swapped1 = t1.swap();
  print(swapped1);

  var t2 = Tuple('Hello', 'Hi');
  final swapped2 = t2.swap();
  print(swapped2);

  var t3 = Tuple(1.00, 1);
  final swapped3 = t3.swap2();
  print(swapped3);
}

class Tuple<L, R> {
  final L left;
  final R right;
  Tuple(this.left, this.right);

  Tuple swap() => Tuple(right, left);

  @override
  String toString() => 'Tuple, left = $left, right = $right';
}

extension on Tuple {
  Tuple swap2() => Tuple(right, left);
}

// 결과
Tuple, left = 20, right = 1
Tuple, left = Hi, right = Hello
Tuple, left = 1, right = 1.0

 

void main(List<String> args) {
  sort(ascending: false);
  print("==============================");
  sort(ascending: true);
}

const ages = [100, 20, 10, 90, 40];
const names = ['Foo', 'Bar', 'Baz'];
const distances = [3.1, 10.2, 1.3, 4.2];

int isLessThan<T extends Comparable>(T x, T y) => x.compareTo(y);
int isGreaterThan<T extends Comparable>(T x, T y) => y.compareTo(x);

void sort({required bool ascending}) {
  final comparator = ascending ? isLessThan : isGreaterThan;
  // spread operator (three dot) []와 같이 쓰면 컬렉션 요소 한 개씩 꺼내 함수 실행한다.
  print([...ages]..sort(comparator));
  print([...names]..sort(comparator));
  print([...distances]..sort(comparator));
}

// 결과
[100, 90, 40, 20, 10]
[Foo, Baz, Bar]
[10.2, 4.2, 3.1, 1.3]
==============================
[10, 20, 40, 90, 100]
[Bar, Baz, Foo]
[1.3, 3.1, 4.2, 10.2]

 

void main(List<String> args) {
  List<dynamic> list = [1, 'John', 10.1];
  final ret1 = getFirstElement(list, 1);
  print(ret1);
  final ret2 = getFirstElement(list, 11);
  print(ret2);
}

// 리턴 타입: T, 함수 파라미터 타입: <T>,
// 입력 파라미터 타입 List<T> <-- 중복 정의처럼 보이는 이유가 함수 파라미터 타입을 T가 아닌
// 다른 타입을 지정할 수도 있으니까
T getFirstElement<T>(List<T> list, int index) {
  if (index < list.length) {
    return list[index];
  } else {
    throw Exception('Index out of bound.');
  }
}

// 결과
John
Unhandled exception:
Exception: Index out of bound.
...

 

// 이런 구조가 있다고 할 때...

void main(List<String> args) {}

// 코드가 중복인 이런 상속 구조가 있다면...
// mixin을 도입하기 좋은 상태이다.

class Animal {
  // all of the class's common method.
  breathe() => print('Breathing...');
}

class Dolphin extends Animal {
  swim() => print('Swimming...');
}

class Shark extends Animal {
  swim() => print('Swimming...');
}

class Bat extends Animal {
  fly() => print('Flying...');
}

class Dove extends Animal {
  fly() => print('Flying...');
}

class Dog extends Animal {
  swim() => print('Swimming...');
}

 

void main(List<String> args) {
  Dog d = Dog();
  d.breath();
  d.swim();

  Bat b = Bat();
  b.breath();
  b.fly();

  // Swimming swimming = Swimming(); 믹스인은 객체 생성이 안 된다.
}

class Animal {
  breath() => print('Breathing...');
}

// 이런 계층 관계를 형성한 경우
// Animal 클래스의 내용을 변경한 경우 mixin으로 끼워 넣은 subClass 들의
// 수정 없이 Animal 클래스의 변경 내용이 적용된다.
class Dog extends Animal with Swimming {}

class Bat extends Animal with Flying {}

mixin Flying {
  fly() => print('Flying...');
}

mixin Swimming {
  swim() => print('Swimming...');
}

// 결과
Breathing...
Swimming...
Breathing...
Flying...

 

void main(List<String> args) {
  // Mixin: adding features to a class
  // Mixins are way of reusing a class's code in multiple class hierarchies.

  // Animal().move();
  // Fish().move();
  Bird().move();

  print("==============================");

  Fish().move();
}

class Animal {
  move() {
    print('position changed.');
  }
}

class Fish extends Animal {
  @override
  move() {
    super.move();
    print('by swimming');
  }
}

class Bird extends Animal {
  @override
  move() {
    super.move();
    print('by flying');
  }
}

// not working // class Duck extends Fish, Bird {}

mixin CanSwim {
  swim() {
    print('position changed by swimming');
  }
}

mixin CanFly {
  fly() {
    print('position changed by flying');
  }
}

class Duck extends Animal with CanSwim, CanFly {}

// 그러면, 원래 있던 Fish 클래스내 메서드 또한 대체 가능하다.
class Fish2 extends Animal with CanSwim {}

// 결과
position changed.
by flying
==============================
position changed.
by swimming

 

// Generics
void main(List<String> args) {
  IntegerDataReader idr = IntegerDataReader();
  final retInt = idr.readData();
  print(retInt);

  print("==============================");

  StringDataReader sdr = StringDataReader();
  final retStr = sdr.readData();
  print(retStr);

  print("==============================");

  final retSome1 = someMethod('str');
  print("type is $retSome1");

  final retSome2 = someMethod(1);
  print("type is $retSome2");
}

String someMethod<T>(T arg) {
  return (arg.runtimeType.toString());
}

abstract class DataReader<T> {
  T readData();
}

class IntegerDataReader implements DataReader {
  @override
  int readData() {
    print('performing logic');
    return 1;
  }
}

class StringDataReader implements DataReader {
  @override
  String readData() {
    print('performing logic');
    return 'hello world';
  }
}

// 결과
performing logic
1
==============================
performing logic
hello world
==============================
type is String
type is int

 

import 'package:meta/meta.dart';

// Immutable Data Class
void main(List<String> args) {
  final person = Person(name: 'John', age: 15);
  // copy of immutable object
  final personUpdated = Person(name: person.name, age: person.age + 1);
  // using copyWith method
  final personUpdated2 = person.copyWith(name: 'Emma', age: person.age + 10);
  print(personUpdated2.name);
  print(personUpdated2.age);
}

// pubspec.yaml
//
// dependencies:
//  meta: ^1.3.0

@immutable
class Person {
  final String name;
  final int age;

  const Person({
    required this.name,
    required this.age,
  });

  Person copyWith({
    String? name,
    int? age,
  }) {
    return Person(
      name: name ?? this.name,
      age: age ?? this.age,
    );
  }
}

// 결과
Emma
25

 

import 'package:meta/meta.dart';

void main(List<String> args) {
  final admin =
      Admin(specialAdminField: 100, firstName: 'John', lastName: 'Doe');
  print(admin.fullName);

  final user = admin;
  print(user.fullName);
  print(user is Admin);
  user.signOut();
}

class User {
  final String _firstName;
  final String _lastName;

  User(this._firstName, this._lastName);

  String get fullName => "$_firstName $_lastName";

  @mustCallSuper
  signOut() {
    print("Signing out.");
  }
}

class Admin extends User {
  final double specialAdminField; // 이 클래스에만 정의한 멤버

  Admin({
    required this.specialAdminField, // this가 붙는 건 이 클래스의 멤버에 넣겠다는 의미
    required String firstName, // 생성자 메서드에서 받는 인자
    required String lastName, // 생성자 메서드에서 받는 인자
  }) : super(firstName, lastName); // 생성자 메서드의 파라미터를 받아 수퍼 클래스의 생성자에 toss

  @override
  String get fullName => "Admin: ${super.fullName}";

  @override
  signOut() {
    print("Performing admin-specific sign out steps.");
    return super.signOut();
  }
}

// 결과
Admin: John Doe
Admin: John Doe
true
Performing admin-specific sign out steps.
Signing out.

 

void main(List<String> args) {
  final user2 = User2(firstName: 'John', lastName: 'Doe', photoUrl: 'http://somepics.com/xyz');
  if (user2.hasLongName()) {
    print(user2.name);
  }
}

class User {
  late String name; // 생성자 바디에서 초기화를 하지만 늦게 되므로 late 추가
  String photoUrl;

  User({
    required String firstName,
    required String lastName,
    required this.photoUrl,
  }) {
    name = "$firstName $lastName";
  }
}

class User2 {
  String name;
  String photoUrl;

  User2({
    required String firstName,
    required String lastName,
    required this.photoUrl,
  }) : name = "$firstName $lastName"; // 필드에서 : 통해 초기화 바로 시도

  bool hasLongName() => name.isNotEmpty;
}

// 결과
John Doe

 

// Classes Properties
// 필드에 들어가는 값에 대한 validation을 하는 방법
void main(List<String> args) {
  final user = User(firstName: 'John', lastName: 'Doe', email: 'j@d.com');
  print(user.getFullName());
  print(user.fullName);
  print(user.email);
}

class User {
  final String firstName;
  final String lastName;
  String? _email;

  User({
    required this.firstName,
    required this.lastName,
    required String email,
  }) {
    this.email = email; // this.name은 setter의 email이고, email 은 파라미터의 email이다.
  }

  String getFullName() =>
      "$firstName $lastName"; // but not followed by the style guide
  String get fullName => "$firstName $lastName"; // getter

  String get email =>
      _email ?? "Email not present."; // 패키지 외부에서 액세스하기 위한 getter

  set email(String email) {
    if (email.contains('@')) {
      _email = email;
    } else {
      _email = null;
    }
  }
}

// 결과
John Doe
John Doe
j@d.com

 

/*
Subclassing
  - When and how to use subclassing?
    - Not always easy
    - Subclassing is not always the right solution
    - Alternative: Composition
*/

class Animal {
  final int age;

  Animal({required this.age});
}

class Dos extends Animal {
  // 이 클래스에는 멤버가 없고 수퍼 클래스에 있기 때문에
  // 생성자 아규먼트에 타입을 지정한다.
  Dos({required int age}) : super(age: age);
}

void main(List<String> args) {}

 

import 'dart:math';

/*
We can use abstract classes to define an interface that
can be implemented by subclasses.

Very powerful: decouples code that uses an interface from its implementation.

=> printArea() doesn't need to know that Square and Circle even exist.

Code with abstractions, to be independent from specific implementations.

(often) a good idea to code against 
  abstract interfaces vs concrete implementations.

*/

void main(List<String> args) {
  // final square = Square(side: 10);
  // printArea(square);

  // You can always assign an instance of a subclass
  // to a variable of the parent class

  final Shape square = Square(side: 10);
  final Shape circle = Circle(radius: 5);

  final List<Shape> shapes = [square, circle];
  shapes.forEach((element) => print(element.area));
  print('');
  shapes.forEach(printArea);
}

abstract class Shape {
  double get area;
}

class Square extends Shape {
  final double side;

  Square({required this.side});

  @override
  double get area => side * side;
}

class Circle extends Shape {
  final double radius;

  Circle({required this.radius});

  @override
  double get area => radius * radius * pi;
}

void printArea(Shape shape) {
  print(shape.area);
}

// 결과
100.0
78.53981633974483
100.0
78.53981633974483

 

// The equality operator and the 'covariant' keyword

void main(List<String> args) {
  print(Point(x: 0, y: 0) == Point(x: 0, y: 0));
  // The argument type 'String' can't be assigned to the parameter type 'Point?'
  // print(Point(x: 0, y: 0) == '00');
}

class Point {
  final int x;
  final int y;

  Point({required this.x, required this.y});

  @override
  String toString() => 'Point($x, $y)';

  @override
  // Use covariant to change the type of an argument
  // when overriding a method
  bool operator ==(covariant Point p) {
    return x == p.x && y == p.y;
  }

  // @override
  // bool operator ==(Object p) {
  //   // bool operator ==(Object p) {
  //   if (p is Point) {
  //     return x == p.x && y == p.y;
  //   }
  //   return false;
  // }
}

// 결과
true

 

void main(List<String> args) {
  final s1 = Stack<int>();
  s1.push = 1;
  s1.push = 2;
  s1.push = 3;

  s1.showNow();
  s1.pop;
  s1.showNow();
  s1.pop;
  s1.showNow();

  print('');

  final s2 = Stack<String>();
  s2.push = 'a';
  s2.push = 'b';
  s2.push = 'c';
  s2.showNow();
  s2.pop;
  s2.showNow();
  s2.pop;
  s2.showNow();
}

class Stack<T> {
  final List<T> _stacks = [];

  set push(T item) => _stacks.add(item);
  T get pop => _stacks.removeLast();

  void showNow() {
    print(_stacks);
  }
}

// 결과
[1, 2, 3]
[1, 2]
[1]

[a, b, c]
[a, b]
[a]

 

/*
===============================
Flutter widget hierachy example
===============================

Composition & inheritance are important.

Composition <-> has-a relationships
Inheritance <-> is-a relationships

*/

void main(List<String> args) {
  final button = Button(
    child: Text('hi'),
    onPressed: () => print('button pressed!'),
  );
}

abstract class Widget {}

class Text extends Widget {
  final String text;
  Text(this.text);
}

class Button extends Widget {
  final Widget child;
  final void Function()? onPressed;

  Button({required this.child, required this.onPressed});
}

 

import 'dart:math';

void main(List<String> args) {
  final shapesJson = [
    {
      'type': 'square',
      'side': 10.0,
    },
    {
      'type': 'circle',
      'radius': 5.0,
    }
  ];

  final shape = shapesJson.map((shapeJson) => Shape.fromJson(shapeJson));
  shape.forEach(printArea);

  print('');

  for (var shapeJson in shapesJson) {
    Shape s = Shape.fromJson(shapeJson);
    printArea(s);
  }

  /*
  Regular constructors must return an instance of the class itself.
  Factory constructors can return an instance of a subclass.
  */
}

abstract class Shape {
  double get area;

  const Shape();

  factory Shape.fromJson(Map<String, Object> json) {
    final type = json['type'];
    switch (type) {
      case 'square':
        final side = json['side'];
        if (side is double) {
          return Square(side: side);
        }
        throw UnsupportedError('invalid or missing side property');
      case 'circle':
        final radius = json['radius'];
        if (radius is double) {
          return Circle(radius: radius);
        }
        throw UnsupportedError('invalid or missing radius property');
      default:
        throw UnimplementedError('shape $type not recognized');
    }
  }
}

class Square extends Shape {
  final double side;

  const Square({required this.side});

  @override
  double get area => side * side;
}

class Circle extends Shape {
  final double radius;

  const Circle({required this.radius});

  @override
  double get area => radius * radius * pi;
}

void printArea(Shape shape) {
  print(shape.area);
}

// 결과
100.0
78.53981633974483
100.0
78.53981633974483

 

// JSON Serialization

void main(List<String> args) {
  final person = Person.fromJson({
    'name': 'John',
    'age': 36,
  });

  final json = person.toJson();
  print(json);
}

class Person {
  final String name;
  final int age;

  Person({required this.name, required this.age});

  factory Person.fromJson(Map<String, dynamic> json) {
    final name = json['name'];
    final age = json['age'];

    if (name is String && age is int) {
      return Person(name: name, age: age);
    }
    throw StateError('Could not read name or age.');
  }

  Map<String, Object> toJson() => {
        'name': name,
        'age': age,
      };
}

// 결과
{name: John, age: 36}

 

// Copying objects with 'copyWiith'
//
// If you need copy-behaviour in your immutable classes,
// create a copyWith method.
// (this is a convention, often used in Flutter)

void main(List<String> args) {
  const credentials = Credentials();
  final updated1 = credentials.copyWith(email: 'me@example.com');
  print(updated1);
  final updated2 = updated1.copyWith(password: 'too-easy');
  print(updated2);
}

class Credentials {
  final String email;
  final String password;

  const Credentials({this.email = '', this.password = ''});

  Credentials copyWith({
    String? email,
    String? password,
  }) {
    return Credentials(
      email: email ?? this.email,
      password: password ?? this.password,
    );
  }

  @override
  String toString() => 'Credentials($email, $password)';
}

// 결과
Credentials(me@example.com, )
Credentials(me@example.com, too-easy)

 

import 'dart:math';

/*
  The cascade operator
    - Cascade operator is just syntactic sugar
    (makes it easier to call multiple methods on the same object)
*/

void main(List<String> args) {
  final path = ClosedPath()
    ..moveTo(Point(0, 0))
    ..lineTo(Point(2, 0))
    ..lineTo(Point(2, 2))
    ..lineTo(Point(0, 2))
    ..lineTo(Point(0, 0));

  // square shape
  // path.moveTo(Point(0, 0));
  // path.lineTo(Point(2, 0));
  // path.lineTo(Point(2, 2));
  // path.lineTo(Point(0, 2));
  // path.lineTo(Point(0, 0));

  print(path._points);
}

class ClosedPath {
  List<Point> _points = [];

  void moveTo(Point point) {
    _points = [point];
  }

  void lineTo(Point point) {
    _points.add(point);
  }
}

// 결과
[Point(0, 0), Point(2, 0), Point(2, 2), Point(0, 2), Point(0, 0)]

 

void main(List<String> args) {
  String city = 'seoul';

  // 이 아래 계층의 클래스에서 무슨 일이 일어 나든지 이 코드를 변경하지 않아도 된다.
  final repo = DataRepository();
  final temperature = repo.fetchTemperature(city);
  print('the weather in $city is $temperature');
}

abstract class DataRepository {
  double fetchTemperature(String city);
  factory DataRepository() => FakeWebServer();
}

class FakeWebServer implements DataRepository {
  @override
  double fetchTemperature(String city) => 42.0;
}

// 결과
the weather in seoul is 42.0

 

import 'dart:math';

void main(List<String> args) {
  Square square = Square(side: 10.0);
  Rectangle rectangle = Rectangle(length: 20, width: 10);
  Circle circle = Circle(radius: 2.0);

  print(square.name);
  print(rectangle.area);
  print(circle.perimeter);

  Shape randShape;

  Random random = Random();
  int choice = random.nextInt(3);
  switch (choice) {
    case 0:
      randShape = Circle(radius: random.nextInt(10) + 1.0);
      print(randShape.name);
      print(randShape.area);
      print(randShape.perimeter);
      break;
    case 1:
      randShape = Rectangle(
          length: random.nextInt(10) + 1.0, width: random.nextInt(10) + 1.0);
      print(randShape.name);
      print(randShape.area);
      print(randShape.perimeter);
      break;
    case 2:
      randShape = Square(side: random.nextInt(3) + 1.0);
      print(randShape.name);
      print(randShape.area);
      print(randShape.perimeter);
      break;
    default:
      print('will never execute');
  }
}

abstract class Shape {
  double get perimeter;
  double get area;
  String get name;
}

class Circle extends Shape {
  double radius;
  Circle({required this.radius});

  @override
  double get area => radius * radius * pi;
  @override
  String get name => 'I am a circle with rafiud $radius';
  @override
  double get perimeter => radius * 2 * pi;
}

class Rectangle extends Shape {
  double length, width;
  Rectangle({required this.length, required this.width});

  @override
  double get area => length * width;
  @override
  String get name => 'I am a rectangle with length: $length and width: $width';
  @override
  double get perimeter => length * 2 + width * 2;
}

class Square extends Rectangle {
  Square({required double side}) : super(length: side, width: side);

  @override
  String get name => 'I am a square with side of $length';
}

// 결과
I am a square with side of 10.0
200.0
12.566370614359172
I am a rectangle with length: 2.0 and width: 10.0
20.0
24.0

 

/*
 "Avoid using getters/setters unnecessarily for field wrapping."

Your class can have its fields changed to getters and setters at any point
without causing any disruption to the code that utilizes it.

When implementing getters/setters, it is important to avoid 
costly computations and any side effects, except for when the setter sets the
backing field.

Using getters and setters is generally advisable when no further
parameters are required and these conditions are met.

It is important to note that shorthand function notation cab be used for
functions besides getters and setters, and is not mandatory. Additionally,
block bodies can also be used for getters and setters.

It is unnecesary to Use getter and setter methods solely for the purpose
of being cautious when it comes to encapsulating fields.

In Dart, there is no distinction between fields and getters/setters,
which means that you can easily expose a field in a class and later
convert it into a getter and setter without making any changes
to the code that relies on that field.
*/

// main.dart

import 'checkingaccount.dart';
import 'savingsaccount.dart';

void main(List<String> args) {
  var account = SavingsAccount(balance: 10000, rate: 10);
  account.addInterest();

  print(account.balance);
  print(account.rate);

  print('=====' * 5);

  var account2 = CheckingAccount(balance: 10000);
  account2.balance = 20000;
  account2.deposit(1000); // 서브 클래스의 메서드 호출 (수퍼 클래스의 메서드 아님)
  print(account2.balance);
}


// 수퍼 클래스
class BankAccount {
  double _balance;

  BankAccount({required double balance}) : _balance = balance;

  double get balance => _balance;

  set balance(double amount) => _balance = amount;

  void deposit(double amount) => _balance += amount;

  void withDraw(double amount) {
    if (amount < 0) print('Invalid amound!');
    if (_balance > amount) {
      _balance -= amount;
    } else {
      print('Insufficient funds!');
    }
  }
}

// checkingaccount.dart

import 'bankaccount.dart';

// It's a more specialize version of BankAccount class.
class CheckingAccount extends BankAccount {
  CheckingAccount({required double balance}) : super(balance: balance);

  @override
  void deposit(double amount) {
    super.deposit(amount - 12.50);
  }

  @override
  void withDraw(double amount) {
    super.withDraw(amount + 12.50);
  }
}

// savingsaccount.dart

import 'bankaccount.dart';

// 수퍼 클래스를 상속하고 있지만
// 추가 메서드를 구현
class SavingsAccount extends BankAccount {
  double _rate;

  SavingsAccount({required double balance, required double rate})
      : _rate = rate,
        super(balance: balance);

  double get rate => _rate;
  set rate(double percentage) => _rate = percentage;

  void addInterest() {
    balance *= (1 + _rate / 100);
  }
}


// 결과
SavingsAccount class: 11000.0
SavingsAccount class: 10.0
=========================
CheckingAccount class: 20987.5

 

void main(List<String> args) {
  // var rectangle = Rectangle(length: 10, width: 20);

  var square = Square(side: 10);
  print(square.getArea());

  print('=====' * 3);

  var cube = Cube(side: 12);
  print(cube.getArea()); // Square method
  print(cube.getVolume()); // Cube method
}

abstract class Rectangle {
  double _length;
  double _width;

  Rectangle({
    required length,
    required width,
  })  : _length = length,
        _width = width;

  double get length => _length;
  set length(double length) => _length = length;

  double get width => _width;
  set width(double width) => _width = width;

  // An abstract class can have both concrete and abstract methods.
  double getArea();
}

class Square extends Rectangle {
  Square({required double side}) : super(length: side, width: side);

  @override
  double getArea() => length * width;
}

class Cube extends Square {
  double _height;

  Cube({required double side})
      : _height = side,
        super(side: side);

  double get height => _height;
  set height(double height) => _height = height;

  double getVolume() => getArea() * height;
}

// 결과
100.0
===============
144.0
1728.0

 

// Composition (has - a relationship)

void main(List<String> args) {
  var course = Course(
    name: 'Dart Programming',
    book: Book(bookName: 'Advanced Dart Programming', isbn: '123-45-678'),
    instructor: Instructor(name: 'John Rambo'),
  );

  print(course);
}

class Course {
  String _name;
  Book _book;
  Instructor _instructor;

  Course(
      {required String name,
      required Book book,
      required Instructor instructor})
      : _name = name,
        _book = book,
        _instructor = instructor;

  String get name => _name;
  set name(String name) => _name = name;

  Book get book => book;
  set book(Book book) => _book = book;

  Instructor get instructor => _instructor;
  set instructor(Instructor instructor) => _instructor = instructor;

  @override
  String toString() {
    return 'Name of Coures: $_name\n$_book\n$_instructor';
  }
}

class Instructor {
  String _name;
  Instructor({required String name}) : _name = name;
  String get name => _name;
  set name(String name) => _name = name;

  @override
  String toString() => 'Name of the Instructor: $_name';
}

class Book {
  String _bookName;
  String _isbn;

  Book({required String bookName, required String isbn})
      : _bookName = bookName,
        _isbn = isbn;

  String get bookName => _bookName;
  set bookName(String bookName) => _bookName = bookName;

  String get isbn => _isbn;
  set isbn(String isbn) => _isbn = isbn;

  @override
  String toString() =>
      'Name of the book: $_bookName\nISBN for the book: $_isbn';
}

// 결과
Name of Coures: Dart Programming
Name of the book: Advanced Dart Programming
ISBN for the book: 123-45-678
Name of the Instructor: John Rambo

 

// Flutter composition example

void main(List<String> args) {
  var button = Button(
    child: Text(name: 'Click me'),
    onPressed: () {},
  );
}

abstract class Widget {}

class Text extends Widget {
  String name;

  Text({required String name}) : this.name = name;
}

class Button extends Widget {
  Widget child;
  Function onPressed;

  Button({required Widget child, required Function onPressed})
      : this.child = child,
        this.onPressed = onPressed;
}

 

void main(List<String> args) {
  Pub pub = Pub();

  try {
    // order of execution is impotant!
    pub.checkAge(Person(name: 'Jimmy', age: 40));
    pub.checkAge(Person(name: 'Jacky', age: 23));
    pub.checkAge(Person(name: 'Jonny', age: 12));
  } catch (e) {
    print(e);
  }

  print(pub.drinking);
}

class Person {
  String name;
  int age;

  Person({required this.name, required this.age});

  @override
  String toString() {
    return name;
  }
}

class Pub {
  List<Person> drinking = [];

  void checkAge(Person p) {
    if (p.age < 18) {
      throw TooYoungException(p);
    } else {
      drinking.add(p);
    }
  }
}

class TooYoungException implements Exception {
  Person p;

  TooYoungException(this.p);

  @override
  String toString() {
    return '${p.name} is too young for the pub!';
  }
}

// 결과
Jonny is too young for the pub!
[Jimmy, Jacky]
반응형

관련글 더보기

댓글 영역