# Home

Joonas의 Today I Learned

> Today I Learned

![](https://4141123541-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MGNgdThR1z0dmeYuF3Z%2F-MGxvmKOGi1ZZDp0OUY1%2F-MGxwUrHOp3wiwCZ6NZY%2Fthumbnail.png?alt=media\&token=3a32befd-513f-4191-9b31-d6dd524ca1e3)


# Chrome Extension

크롬 확장 프로그램과 관련된 주제들 정리


# CSS injection

팝업창이나 background에서 동작한다면, `chrome.tabs.insertCSS` 를 사용할 수 있다.

content\_script 등을 지정해서 특정 조건에서만 동작하게 했다면, 위 메소드는 사용할 수 없다.

대신, 동적으로 파일을 읽어서 head에 추가하는 방법이 있는데, 그러기 위해서는 먼저 그 파일을 ["web\_accessible\_resources"](https://developer.chrome.com/extensions/manifest/web_accessible_resources) 에 추가하여 권한을 먼저 등록해주어야한다.

Links

* <https://stackoverflow.com/questions/33716881/chrome-extension-how-to-insert-css-conditioned-on-variable-from-chrome-storage/33719884>


# Design Pattern

디자인 패턴은 자주 마주치는 오류를 줄이기 위한 패턴을 이야기하고 있다.

유지 보수에 있어서 프로젝트를 더욱 견고하게 만드는 것이다.

그러니 패턴에 얽매여 강제로 도입할 필요는 없는 것이다. 문제가 발생하였을 때 해결책으로 강구하기에 좋은 것임을 잊지 말자.


# SOLID 원칙

SOLID 원칙들은, 소프트웨어 작업에서 프로그래머가 소스 코드를 **읽기 쉽고 확장하기 쉽게** 될 때 까지 리팩토링하여 [**코드 냄새**](https://ko.wikipedia.org/wiki/%EC%BD%94%EB%93%9C_%EC%8A%A4%EB%A9%9C)**를 없애기 위해** 쓰기 좋은 지침이다.

## 1. Single Responsibility Principle (SRP; 단일 책임 원칙)

{% content-ref url="/pages/-MGs-AJp0fPw6azAZ6Nm" %}
[1. SRP](/design-pattern/solid-priciple/1-srp)
{% endcontent-ref %}

## 2. Open/Closed Principle (OCP; 개방-폐쇄 원칙)

{% content-ref url="/pages/-MGs-zRrkos6Zp1FM-gU" %}
[2. OCP](/design-pattern/solid-priciple/2-ocp)
{% endcontent-ref %}

## 3. Liskov Substitution principle (LSP; 리스코프 치환 원칙)

{% content-ref url="/pages/-MGs028J4Q1EdPk2qFEs" %}
[3. LSP](/design-pattern/solid-priciple/3-lsp)
{% endcontent-ref %}

## 4. Interface Segregation Principle (ISP; 인터페이스 분리 원칙)

{% content-ref url="/pages/-MGs0BlA6RfT0maQuLwH" %}
[4. ISP](/design-pattern/solid-priciple/4-isp)
{% endcontent-ref %}

## 5. Dependency Inversion Principle (DIP; 의존관계 역전 원칙)

{% content-ref url="/pages/-MGs0EhTCzgh6\_g5wSyX" %}
[5. DIP](/design-pattern/solid-priciple/5-dip)
{% endcontent-ref %}


# 1. SRP

Single Responsibility Principle (SRP; 단일 책임 원칙)

## Single Responsibility Principle (SRP; 단일 책임 원칙)

> 하나의 클래스나 모듈은 단 하나의 책임만 가져야 한다.

하나의 함수가 여러 개의 일을 한다는 뜻은 예측 불가능하다는 의미이다.

### 예시

아주 단적인 예시로, 끝 원소를 제거를 하는 함수가 삭제되는 원소를 반환할 이유는 없다. 제거만 하면 된다.

```cpp
Element pop() {
    if (size < 1) throw "Empty";
    size = size - 1;
    return array[size + 1]; // ?
}
```

삭제되는 값을 얻고 싶다면, `pop` 하기 전에 끝자리를 `get` 하는 것이 옳다. 삭제는 삭제만, 조회는 조회만 하는 것이 예측할 수 없는 사이드 이펙트를 줄일 수 있기 때문이다.

```cpp
void pop() {
    size = size - 1;
}

Element get() {
    return array[size - 1];
}
```

즉, 기능을 더 쪼갤 수 없을 만큼 아토믹(atomic)한 것이 좋다.

## Links

* [https://ko.wikipedia.org/wiki/SOLID\_(객체\_지향\_설계)](https://ko.wikipedia.org/wiki/SOLID_\(%EA%B0%9D%EC%B2%B4_%EC%A7%80%ED%96%A5_%EC%84%A4%EA%B3%84\))
* [https://ko.wikipedia.org/wiki/단일\_책임\_원칙](https://ko.wikipedia.org/wiki/%EB%8B%A8%EC%9D%BC_%EC%B1%85%EC%9E%84_%EC%9B%90%EC%B9%99)


# 2. OCP

Open/Closed Principle (OCP; 개방-폐쇄 원칙)

## Open/Closed Principle (OCP; 개방-폐쇄 원칙)

> 소프트웨어 요소는 확장에는 열려 있으나, 변경에는 단혀 있어야 하다.

모듈 중 하나를 수정했는데, 그 모듈을 사용하는 모든 모듈의 코드를 수정하는 일이 있으면 안된다는 뜻이다.

조금만 떠올려봐도 얼마나 끔찍한 일인 지 알 수 있다.

### **확장에 대해 열려 있다.**

> 이것은 모듈의 동작을 확장할 수 있다는 것을 의미한다.&#x20;
>
> 애플리케이션의 요구 사항이 변경될 때, 이 변경에 맞게 새로운 동작을 추가해 모듈을 확장할 수 있다. 즉, 모듈이 하는 일을 변경할 수 있다.

### **수정에 대해 닫혀 있다.**

> 모듈의 소스 코드나 바이너리 코드를 수정하지 않아도 모듈의 기능을 확장하거나 변경할 수 있다.
>
> 그 모듈의 실행 가능한 바이너리 형태나 링크 가능한 라이브러리 (예를 들어, Windows의 DLL, 자바의 jar ...)를 건드릴 필요가 없다.

### 추상화

객체 지향 프로그래밍 언어에서는 "추상화"를 통해서 가능할 수 있다.

추상화 클래스나 인터페이스를 만들어, 상위 클래스는 **수정할 수 없도록 만들되** 서브 클래스에서는 기능을 추가하여 **확장할 수 있도록 만들기** 때문이다.

## Links

* [https://ko.wikipedia.org/wiki/SOLID\_(객체\_지향\_설계)](https://ko.wikipedia.org/wiki/SOLID_\(%EA%B0%9D%EC%B2%B4_%EC%A7%80%ED%96%A5_%EC%84%A4%EA%B3%84\))
* [https://ko.wikipedia.org/wiki/개방-폐쇄\_원칙](https://ko.wikipedia.org/wiki/%EA%B0%9C%EB%B0%A9-%ED%8F%90%EC%87%84_%EC%9B%90%EC%B9%99)


# 3. LSP

Liskov Substitution principle (LSP; 리스코프 치환 원칙)

## Liskov Substitution principle (LSP; 리스코프 치환 원칙)

> 프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다.

어떤 모듈 S가 모듈 T의 하위 모듈이라면, 속성의 변경없이 T를 S(상위)로 교체할 수 있어야 한다고 한다.\
즉, 부모 클래스와 자식 클래스가 일관되어야 한다는 뜻이다.

### 위반 사례

이를 위반하는 대표적인 사례로는 원-타원 문제 (또는 사각형-정사각형 문제)가 있다.

```cpp
class Rectangle {
  private int width;
  private int height;

  public void setWidth(int width){
    this.width = width;
  }

  public void setHeight(int height){
    this.height = height;
  }

  public int getArea() {
    return this.width * this.height;
  }
}

// Test
public void testArea(Rectangle rect) {
  rect.setWidth(5);
  rect.setHeight(4);
  System.out.println("area = " + rect.getArea()); // it must be 20.
}
```

사각형을 표현하는 `Rectangle` 라는 클래스가 있다.\
그리고 두 사각형의 넓이를 구하는 테스트 함수 `testArea()`가 있다.

그리고 하위 클래스로 정사각형을 표현하는 `Square` 라는 클래스를 만들었다.

```cpp
class Square extends Rectangle {
}

public void main(String[] args) {
  testArea(new Rectangle());
  testArea(new Square());
}
```

**그런데 뭔가 이상하다. 두 사각형의 넓이가 모두 20이 나온다.**

정사각형의 너비(width)와 높이(height)가 다르게 설정되어서이다. 그럼 정사각형이 아니지 않은가.\
그럼 `Square` 클래스를 이렇게 수정해보자.

```cpp
class Square extends Rectangle {
  @Override
  public void setWidth(int width) {
    super.setWidth(width);
    super.setHeight(width);
  }
  
  @Override
  public void setHeight(int height) {
    super.setWidth(height);
    super.setHeight(height);
  }
}
```

이제 정사각형의 너비나 넓이 중 무엇을 설정하여도, 두 값을 변경하여 정사각형으로 만들고 있다.

**... 그런데 이럴거면 `Rectangle` 클래스를 왜 상속한거지?**\
정사각형에게 "너비 설정"과 "높이 설정"이 굳이 따로 있을 이유가 없다. 오히려 혼란스럽다.

즉, 동작에 있어서 상위 클래스인 `Rectangle` 과 `Square` 는 서로 일관되게 행동하지 않는다.

### 해결

위의 두 사각형 클래스는 포함 관계를 가지지 않으므로, 부모-자식이 아닌 형제(sibling)관계로 만들어 해결할 수 있다.

![Rectalges and Square - LSP compliant solution](https://4141123541-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MGNgdThR1z0dmeYuF3Z%2F-MGk4h5G25op7S88drUp%2F-MGkf6wiCB3Le6cdiDbj%2Fimage.png?alt=media\&token=23498836-b7d7-4953-99d7-35581193ebd4)

상위에 `Shape` 라는 클래스를 두어 형제 관계로 만들 수도 있다.

## Links

* [https://ko.wikipedia.org/wiki/SOLID\_(객체\_지향\_설계)](https://ko.wikipedia.org/wiki/SOLID_\(%EA%B0%9D%EC%B2%B4_%EC%A7%80%ED%96%A5_%EC%84%A4%EA%B3%84\))
* [https://dev-momo.tistory.com/entry/SOLID-원칙](https://dev-momo.tistory.com/entry/SOLID-%EC%9B%90%EC%B9%99)
* <https://dzone.com/articles/the-liskov-substitution-principle-with-examples>
* <http://stg-tud.github.io/sedc/Lecture/ws13-14/3.3-LSP.html>


# 4. ISP

Interface Segregation Principle (ISP; 인터페이스 분리 원칙)

## Interface Segregation Principle (ISP; 인터페이스 분리 원칙)

> 특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.

### 위반 사례

이 ATM기는 총 3개의 모듈을 가지고 있는데, 어떤 거래(transaction)에 대해서 입금(Deposit), 출금(Withdrawal), 송금(Transfer) 모듈을 각자 만들었다.

![](https://flylib.com/books/4/444/1/html/2/images/12_5.jpg)

각 모듈은 전문화된 기능을 가지기 위해 분리되었지만, 무언가 이상하다.

입금 거래 모듈은 입금만 하면 되는데, 위 구조와 같다면 코드가 이럴것이다.

{% tabs %}
{% tab title="DepositTransaction" %}

```java
public class DepositTransaction extends Transaction implements UI {
  @Override public void requestDepositAmount() {
    System.out.println("Request deposit");
  }

  @Override public void requestWithdrawalAmount() {
    throw new UnsupportedMethodException("Can not withdrawal at Deposit Transaction");
  }

  @Override public void requestTransferAmount() {
    throw new UnsupportedMethodException("Can not transfer at Deposit Transaction");
  }

  @Override public void execute() {
    requestDepositAmount();
  }
}
```

{% endtab %}

{% tab title="WithdrawalTransaction" %}

```java
public class WithdrawalTransaction extends Transaction implements UI {
  @Override public void requestDepositAmount() {
    throw new UnsupportedMethodException("Can not deposit at Withdrawal Transaction");
  }

  @Override public void requestWithdrawalAmount() {
    System.out.println("Request withdrawal");
  }

  @Override public void requestTransferAmount() {
    throw new UnsupportedMethodException("Can not transfer at Withdrawal Transaction");
  }

  @Override public void execute() {
    requestWithdrawalAmount();
  }
}
```

{% endtab %}

{% tab title="TransferTransaction" %}

```java
public class TransferTransaction extends Transaction implements UI {
  @Override public void requestDepositAmount() {
    throw new UnsupportedMethodException("Can not deposit at Transfer Transaction");
  }

  @Override public void requestWithdrawalAmount() {
    throw new UnsupportedMethodException("Can not withdrawal at Transfer Transaction");
  }

  @Override public void requestTransferAmount() {
    System.out.println("Request transfer");
  }

  @Override public void execute() {
    requestTransferAmount();
  }
}
```

{% endtab %}
{% endtabs %}

입금 거래 모듈을 개발하는 입장에서, 송금이 되지 않는다는 걸 작성하는 일은 **필요하지 않고 옳지 못하다.**

### 해결

각 모듈에 맞게 **인터페이스를 분리**하여 해결할 수 있다.

<div align="center"><img src="https://flylib.com/books/4/444/1/html/2/images/12_6.jpg" alt=""></div>

`UI` 인터페이스를 모듈별로 분리하였다. 이제 각 모듈은 사용하는 기능들만 구현하면 된다.

먼저, `UI` 인터페이스를 각 모듈을 위해 쪼개자.

```java
// integrated interface for general
public interface UI extends DepositUI, WithdrawalUI, TransferUI {}

// segregated interface
public interface DepositUI {
    void requestDepositAmount();
}

public interface TransferUI {
    void requestTransferAmount();
}

public interface WithdrawalUI {
    void requestWithdrawalAmount();
}
```

그리고 각 모듈에서 필요한 인터페이스만 가져다가 구현한다.

{% tabs %}
{% tab title="DepositTransaction " %}

```java
public class DepositTransaction extends Transaction {
  private DepositUI depositUI;

  public DepositTransaction(DepositUI ui) {
    depositUI = ui;
  }

  @Override
  public void execute() {
    depositUI.requestDepositAmount();
  }
}
```

{% endtab %}

{% tab title="WithdrawalTransaction" %}

```java
public class WithdrawalTransaction extends Transaction {
  private WithdrawalUI withdrawalUI;

  public WithdrawalTransaction(WithdrawalUI ui) {
    withdrawalUI = ui;
  }

  @Override
  public void execute() {
    withdrawalUI.requestWithdrawalAmount();
  }
}
```

{% endtab %}

{% tab title="TransferTransaction" %}

```java
public class TransferTransaction extends Transaction {
  private TransferUI transferUI;

  public TransferTransaction(TransferUI ui) {
    transferUI = ui;
  }

  @Override
  public void execute() {
    transferUI.requestTransferAmount();
  }
}
```

{% endtab %}
{% endtabs %}

이제 필요한 기능만 구현하면 되는 인터페이스가 준비되었다. 그럼 아래와 같은 코딩이 가능하다.

```java
public void testFunction() {
  Transaction custom = new TransferTransaction(new TransferUI() {
    @Override
    public void requestTransferAmount() {
      System.out.println("Request transfer for me");
    }
  });
  custom.execute();
}
```

### 주의 사항

개발을 하다보면 클라이언트가 사용하지 않는 메소드를 추가하여, 하나의 인터페이스가 거대해지는 일이 잦다.\
쪼개려다보면 애매한 상황도 많고 파일이 많아지는게 또 피곤한 일이라, 굉장히 위배하기 쉬운 원칙이다.

### 전체 구조 및 코드

<https://repl.it/@joonasyoon/SOLID-ISP-ATM>

위 링크에서 실행 결과와 함께 더 자세히 확인해 볼 수 있다

## Links

* [https://ko.wikipedia.org/wiki/SOLID\_(객체\_지향\_설계)](https://ko.wikipedia.org/wiki/SOLID_\(%EA%B0%9D%EC%B2%B4_%EC%A7%80%ED%96%A5_%EC%84%A4%EA%B3%84\))
* <http://stg-tud.github.io/sedc/Lecture/ws16-17/3.2-ISP.pdf>
* <https://reflectoring.io/interface-segregation-principle/>
* [The Interface Segregation Principle, Robert C. Martin, C++ Report, June 1996](https://docs.google.com/a/cleancoder.com/viewer?a=v\&pid=explorer\&chrome=true\&srcid=0BwhCYaYDn8EgOTViYjJhYzMtMzYxMC00MzFjLWJjMzYtOGJiMDc5N2JkYmJi\&hl=en)
* <https://flylib.com/books/en/4.444.1.79/1/>


# 5. DIP

Dependency Inversion Principle (DIP; 의존관계 역전 원칙)

## Dependency Inversion Principle (DIP; 의존관계 역전 원칙)

> 프로그래머는 추상화에 의존해야지, 구체화에 의존하면 안된다.

더 자세히는 이렇게 말한다.

> 상위 계층(정책 결정)이 하위 계층(세부 사항)에 의존하는 전통적인 의존관계를 반전(역전)시킴으로써 상위 계층이 하위 계층의 구현으로부터 독립되게 할 수 있다.
>
> 1. 상위 모듈은 하위 모듈에 의존해서는 안된다. 상위 모듈과 하위 모듈 모두 추상화에 의존해야 한다.
> 2. 추상화는 세부 사항에 의존해서는 안된다. 세부사항이 추상화에 의존해야 한다.

의존 관계가 많을수록 코드의 변경이 잦아지는 것은 당연하다. 변경할 곳이 많다는 의미는 코드를 파악하고 수정하는 일이 무척 어렵다는 뜻이다. 의존 관계에 신경을 쓰는 이유는 이런 부분이다.

추상클래스나 인터페이스는 구현 클래스보다 덜 변화되기 때문에 추상화에 신경써서 의존도를 낮추라는 뜻이다.

### 위반 사례

여기 버튼(Button)과 전등(Lamp)이 있다.

**Button은 외부 환경을 감지하는 객체**이다. 버튼을 눌렀는지 여부만 감지할 뿐이다.

**Lamp는 외부 환경을 변화시키는 객체**이다. `turnOn` 메소드가 호출되면 전등을 켜서 불을 밝힌다.

Button을 눌러서 Lamp를 동작하게 하려면 어떤 구조여야 할까.

![](https://4141123541-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MGNgdThR1z0dmeYuF3Z%2F-MGrw7FoVOfVyHwuYsLD%2F-MGrwEO9ZJRek-X8mWiR%2Fimage.png?alt=media\&token=d27dc0ec-5e88-471b-8188-a17ce5a411be)

Lamp에 Button을 추가하면 해결될까?

```java
class Lamp {
  private Button button;
  public void turnOn();
  public void turnOff();
}
```

동작은 하겠지만 이 방법은 의존 관계 역전 규칙(DIP)을 위반한다. 구체적인 클래스인 `Lamp`에서 `Button`을 포함하기 때문이다.

지금의 `Button`은 `Lamp`의 구현에 완전히 묶여있다. `Button` 이 필요한 모든 클래스(`Computer`, `TV` 등..)에서 전부 선언할 수는 없는 노릇이다.

### 해결

![](https://4141123541-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MGNgdThR1z0dmeYuF3Z%2F-MGrw7FoVOfVyHwuYsLD%2F-MGrwRFl2vqwVv1y4feZ%2Fimage.png?alt=media\&token=e169d628-62d4-4e20-9450-03821f345a3c)

이제 `Button`와 `Lamp` 는 **추상화 된 모듈을 의존**한다. `Lamp`에서 코드가 바뀌더라도 서로 영향을 받지 않는다.

`Button`은 `Switchable` 인터페이스에 의존하고, `Lamp`가 `Switchable`의 인터페이스를 구현하게 되면 **`Lamp`가 `Button`에 의존하는 역전 관계가 되는 것**이다.

이렇게 되면, `Switchable`의 인터페이스를 조작하는 방법을 아는 객체는 `Lamp`를 켜고 끌 수 있다.

```java
interface Switchable {
  void activate();
  void deactivate();
}

class Lamp implements Switchable {
  @Override public void activate() {
    System.out.println("Lamp: turn on");
  }
  
  @Override public void deactivate() {
    System.out.println("Lamp: turn off");
  }
}
```

`Lamp`는 이제 환경에 상관없이 자신의 동작인 `activate`와 `deactivate` 구현에 집중할 수 있다.

![forked from C++ Report by Robert C. Martin](https://4141123541-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MGNgdThR1z0dmeYuF3Z%2F-MGrw7FoVOfVyHwuYsLD%2F-MGrx6mQMULS1NUZzr8K%2Fimage.png?alt=media\&token=bb3b50ad-6846-4c8f-81bc-dcd5801b2cef)

[Robert C. Martin의 레포트](https://web.archive.org/web/20110714224327/http://www.objectmentor.com/resources/articles/dip.pdf)에서는 `Button`도 추상화하였다.\
\
구현 클래스와 추상 클래스를 완전히 분리시킨 것이다.

{% hint style="info" %}
이 부분에는 어댑터 패턴(Adapter Pattern)이 사용되었다.
{% endhint %}

```java
abstract class Button {
  private Switchable client;

  public Button(Switchable client) {
    this.client = client;
  }

  // detect its state and activate/deactivate  
  public void run() {
    boolean isButtonOn = getState();
    if (isButtonOn) {
      client.activate();
    } else {
      client.deactivate();
    }
  }

  abstract boolean getState();
}
```

`Button`도 **구현체에 상관없이 외부 환경을 감지**하여, 참조하는 객체에게 어떤 동작을 일으킬 수 있게 되었다.

```java
class ToggleButton extends Button {
  private boolean isActive = false;
  
  public ToggleButton(Switchable client) {
    super(client);
  }

  // Unique method only for ToggleButton
  public void toggle() {
    this.isActive = !this.isActive;
  }

  @Override public boolean getState() {
    return this.isActive;
  }
}
```

`Button`의 구현 중 하나인 `ToggleButton`은 `toggle()` 을 호출하면 버튼의 ON/OFF 상태가 반전된다.

상태 변화만 다루고 있으며, `Lamp` 따위의 동작에는 어떤 개입도 하지 않는 것을 확인할 수 있다.

```java
class Main {
  public static void main(String[] args) {
    ToggleButton lampButton = new ToggleButton(new Lamp());
    lampButton.run();
    lampButton.toggle();
    lampButton.run();
  }
}
```

`ToggleButton`에 `Lamp`를 위처럼 연결하였다. `Button.run()` 을 호출하면 `Button` 의 상태에 따라 `Lamp` 를 동작시키고 있다.

같은 방식으로 객체 `Computer` 도 추가할 수 있다.

```java
class Computer implements Switchable {
  @Override public void activate() {
    System.out.println("Computer: turn on");
  }

  @Override public void deactivate() {
    System.out.println("Computer: turn off");
  }
}

class Main {
  public static void main(String[] args) {
    ToggleButton computerButton = new ToggleButton(new Computer());
    computerButton.run();
    computerButton.toggle();
    computerButton.run();
  }
}
```

### 전체 코드

{% embed url="<https://repl.it/@joonasyoon/SOLID-DIP>" %}

## Links

* [https://ko.wikipedia.org/wiki/SOLID\_(객체\_지향\_설계)](https://ko.wikipedia.org/wiki/SOLID_\(%EA%B0%9D%EC%B2%B4_%EC%A7%80%ED%96%A5_%EC%84%A4%EA%B3%84\))
* <https://walbatrossw.github.io/oop/2018/07/27/06-solid-dip.html>
* [The Dependency Inversion Principle, Robert C. Martin, C++ Report, May 1996](https://web.archive.org/web/20110714224327/http://www.objectmentor.com/resources/articles/dip.pdf)
* <https://blog.naver.com/jwyoon25/221776517812>
* <http://stg-tud.github.io/sedc/Lecture/ws13-14/3.5-DIP.html>


# 생성 패턴


# Singleton Pattern

싱글턴 패턴(Singleton pattern)

## 싱글턴 패턴(Singleton pattern)

싱글턴 패턴은 인스턴스를 하나만 생성하여 사용하는 패턴이다.

개인적으로 가장 쉬우면서 흔하게 볼 수 있는 패턴이다.

### 정의 및 예시

어떤 게임의 `설정` 클래스가 있다고 하자.

```java
public Config {
    private int volume = 10;
    private int theme = Config.DARK;
}
```

이 설정을 다른 클래스(캐릭터, 키보드 관리자 등)에서도 사용하고 싶다.

```java
public Character {
    private Config config;
}

public Keyboard {
    private Config config;
}
```

두 클래스 `Character`와 `Keyboard`는 같은 설정을 가지고 있을까? 아니다.

이 문제는, 아래처럼 해결할 수 있다.

```java
public Config {
    // static으로 메모리에 할당한다.
    private static Config instance;
    
    // 생성자를 숨겨서, 외부에서는 직접 생성할 수 없도록 한다.
    private Config() {
        // 실제 객체는 한번만 생성된다.
        instance = new Config();
    }
    
    // 외부에서는 가져다 쓰는 것만 가능하다.
    public getInstance() {
        return instance;
    }
}

// 이제 두 클래스는 같은 객체를 참조한다.
public Character {
    private Config config = Config.getInstance();
}

public Keyboard {
    private Config config = Config.getInstance();
}
```

`Config` 클래스의 생성자를 직접 호출하지 못하도록 하여, 인스턴스가 한번만 생성되도록 한다. 대신 `getInstace` 등의 함수로 인스턴스를 가져다 쓸 수만 있도록 제한한다.

### 문제점

메모리에 계속 떠있다보니, 싱글톤으로 만든 인스턴스가 무거우면 문제가 된다.

## Links

* <https://gmlwjd9405.github.io/2018/07/06/singleton-pattern.html>
* <https://jeong-pro.tistory.com/86>


# Abstract Factory Pattern


# Factory Method Pattern


# 구조 패턴


# Adapter Pattern

어댑터 패턴(Adapter Pattern)

## 어댑터 패턴(Adapter Pattern)

> &#x20;**어댑터 패턴**(Adapter pattern)은 클래스의 인터페이스를 사용자가 기대하는 다른 인터페이스로 변환하는 패턴으로, 호환성이 없는 인터페이스 때문에 함께 동작할 수 없는 클래스들이 함께 작동하도록 해준다.

USB 어댑터를 떠올려보자.

어댑터만 있다면, USB A타입에 어댑터를 연결해서 USB-C 타입에 연결하여 정상적으로 사용할 수 있다.

이렇듯 호환되지 않던 인터페이스를 서로 동작할 수 있도록 해주는 패턴이다.

클래스를 감싸서 변환하는 모양 때문에 Wrapper 라고도 불리지만, 이건 다른 패턴과 혼동의 여지가 있다.

### 개요

어댑터 패턴이 해결할 수 있는 문제는 다음과 같다.

* 클라이언트에게는 필요하지만, 그런 인터페이스는 없는 클래스를 어떻게 재사용하지?
* 서로 호환되지 않는 인터페이스를 가진 클래스끼리 어떻게 동작시키지?
* 어떤 클래스를 위한 대체 인터페이스(alternative interface)를 어떻게 제공하지?

보통 재사용이 힘든 원인은, 기존의 클래스와 클라이언트 둘의 인터페이스가 다르기 때문이다.

이렇게 해결할 수 있다.

* `Adapter` 라는 별도의 클래스를 두어서, `Adaptee` 클래스의 인터페이스를 `Target` 클라이언트의 인터페이스로 변환하는 작업을 거친다.
* 필요한 인터페이스가 없는 클래스들은 `Adapter`를 통해서 동작한다.

아래 UML을 보면 이해가 빠르다.

### 구조

두 가지 구조로 설명하는데, 상속(inheritance) 관계를 사용했는 지 포함(composition) 관계를 사용했는 지가 가장 핵심적인 차이이다.

#### Object adapter pattern&#x20;

먼저, 객체를 감싼다면 어댑터 패턴은 아래와 같다.

![Object adapter pattern](https://www.cs.unc.edu/~stotts/GOF/hires/Pictures/adapt104.gif)

`Adapter` 클래스가 멤버 변수로 `Adaptee`를 가지고 있어서, 인터페이스에 맞게 동작시켜 호환하는 방법이다. (포함 관계)

즉, 내부적으로 `adaptee.specRequest()` 와 같이 적절하게 호출시키는 것이다. `Client`의 입장에서 `Adaptee` 객체는 내부를 볼 수 없는 블랙박스(blackbox)가 된다.

#### Class adapter pattern

클래스 어댑터 패턴은 상속(inferitance) 관계로 클래스를 감싼다.

![Class adapter pattern](https://www.cs.unc.edu/~stotts/GOF/hires/Pictures/adapt106.gif)

`Adapter`가 `Target`과  `Adaptee` 클래스를 모두 상속해서 호환되도록 구현하여 동작시키는 것이다.

그런데 이 방식은 다중 상속을 지원해야해서, JAVA(JDK 1.8 이전) 등에서는 불가능하다.

### 예시

{% hint style="warning" %}
언어 스펙 상 객체 어댑터 패턴으로 구현하였다.
{% endhint %}

라이트닝 케이블을 사용하는 아이폰과, 마이크로 USB를 사용하는 안드로이드폰이 있다.

```java
interface LightningPhone {
    void recharge();
    void useLightning();
}

interface MicroUsbPhone {
    void recharge();
    void useMicroUsb();
}

class Iphone implements LightningPhone {}
class Android implements MicroUsbPhone {}
```

두 스마트폰 모두 충전 기능을 가지고 있지만, 아이폰과 마이크로 USB 케이블밖에 없는 상황이라고 하자.

그래서 충전기(클라이언트)에 꽂는 케이블(인터페이스)이 달라 연결할 수 없는 상태이다.

* 아이폰 → 라이트닝 케이블 → 충전기
* 안드로이드폰 → 마이크로 USB → 충전기

{% tabs %}
{% tab title="Iphone" %}

```java
class Iphone implements LightningPhone {
    private boolean connector;

    @Override
    public void useLightning() {
        connector = true;
        System.out.println("Lightning connected");
    }

    @Override
    public void recharge() {
        if (connector) {
            System.out.println("Recharge started");
            System.out.println("Recharge finished");
        } else {
            System.out.println("Connect Lightning first");
        }
    }
}
```

{% endtab %}

{% tab title="Android" %}

```java
class Android implements MicroUsbPhone {
    private boolean connector;

    @Override
    public void useMicroUsb() {
        connector = true;
        System.out.println("MicroUsb connected");
    }

    @Override
    public void recharge() {
        if (connector) {
            System.out.println("Recharge started");
            System.out.println("Recharge finished");
        } else {
            System.out.println("Connect MicroUsb first");
        }
    }
}
```

{% endtab %}
{% endtabs %}

라이트닝 케이블로 마이크로 USB가 호환되도록 하는 어댑터은 다음과 같이 생겼다.

```java
class LightningToMicroUsbAdapter implements MicroUsbPhone {
    private final LightningPhone lightningPhone;

    public LightningToMicroUsbAdapter(LightningPhone lightningPhone) {
        this.lightningPhone = lightningPhone;
    }

    @Override
    public void useMicroUsb() {
        System.out.println("MicroUsb connected"); // MicroUsb
        /* ... any conversion for electric power ... */
        lightningPhone.useLightning();            // lightningPhone
    }

    @Override
    public void recharge() {
        lightningPhone.recharge();
    }
}
```

마이크로 USB를 사용하면, 적절한 처리와 함께 라이트닝 케이블이 동작하도록 것이다.

이제 아래처럼 사용할 수 있다.

* 아이폰 → 라이트닝 케이블 → (어댑터) → 마이크로 USB → 충전기

```java
public class AdapterDemo {
    static void rechargeMicroUsbPhone(MicroUsbPhone phone) {
        phone.useMicroUsb();
        phone.recharge();
    }

    static void rechargeLightningPhone(LightningPhone phone) {
        phone.useLightning();
        phone.recharge();
    }

    public static void main(String[] args) {
        Android android = new Android();
        Iphone iPhone = new Iphone();

        System.out.println("Recharging android with MicroUsb");
        rechargeMicroUsbPhone(android);

        System.out.println("Recharging iPhone with Lightning");
        rechargeLightningPhone(iPhone);

        System.out.println("Recharging iPhone with MicroUsb");
        rechargeMicroUsbPhone(new LightningToMicroUsbAdapter (iPhone));
    }
}
```

### 전체 코드 및 실행

<https://repl.it/@joonasyoon/DesignPattern-Adapter#Main.java>

## Links

* <https://en.wikipedia.org/wiki/Adapter_pattern>
* <https://www.cs.unc.edu/~stotts/GOF/hires/pat4afso.htm>
* <https://yaboong.github.io/design-pattern/2018/10/15/adapter-pattern/>
* <https://stackoverflow.com/questions/9978477/difference-between-object-adapter-pattern-and-class-adapter-pattern>


# Bridge Pattern


# Composite Pattern


# Decorator Pattern

데코레이터 패턴이란,

> &#x20;주어진 상황 및 용도에 따라 어떤 객체에 책임을 덧붙이는 패턴으로,\
> 기능 확장이 필요할 때 서브클래싱 대신 쓸 수 있는 유연한 대안이 될 수 있다.
>
> \- 위키백과

## 앞서보기

이름대로 주변에 기능을 덧붙여서 사용할 수 있도록 설계된 패턴이다.

실제로 사용되는 사례로는 파이썬의 decorator가 있다.

함수를 사용하면 경고 로그를 남기는 `warning` 이라는 데코레이터가 있다고 치자. 그럼 파이썬에서는 아래처럼 코드를 작성할 수 있다.

```python
@warning
def myFunction():
    print('my function')
```

이제 myFunction() 함수는 데코레이터와 함께 동작한다.  (물론 파이썬의 문법에 의해 가능한 것이다. 모양새만 알고 가자.)

데코레이터가 함수 실행 전과 후에 로그를 남기도록 만들어졌다면, 결과는 아래와 같을 수 있다.

```python
warning: function 'myFunction()' called
my function
warning: function 'myFunction()' finished
```

이런 방식은 어떤 기능(메소드)을 추가하기 위해서 클래스를 상속받을 필요도 없고, 새로 생성할 필요도 없다.


# Facade Pattern


# Proxy Pattern


# 행위 패턴


# Command Pattern


# Observer Pattern


# State Pattern


# Strategy Pattern


# Template Method Pattern


# Graphics


# OpenGL ES

{% embed url="<https://doc.lagout.org/programmation/OpenGL/OpenGL%20ES%203.0%20Programming%20Guide%20%282nd%20ed.%29%20%5BGinsburg%20%26%20Purnomo%202014-03-10%5D.pdf>" %}


# 파이프라인

3D 세상에 있는 것들이 2D 화면에 그려지기까지.

OpenGL ES 3.0의 파이프라인 이전에 2.0 버전으로도 하위 호환이 잘 이루어지고 있으므로,\
비교적 간단한 2.0 버전으로 작성한다.

![\[그림 1\] OpenGL ES 2.0 pipeline map](http://www.seas.upenn.edu/~pcozzi/OpenGLInsights/images/pipeline1.png)

파이프라인에 대해 더 잘 설명한 블로그가 많다.

정리된 글 중에서는은 <https://webnautes.tistory.com/1010> 이 가장 읽기 좋았다.


# 삼각형 그리기

Android에서 OpenGL ES 2.0으로 삼각형을 정의하고, SurfaceView에 그려본다.

우선 **OpenGL ES**는 **3차원 공간**에서 좌표로 객체를 그린다.

객체는 각 점(vertex)들을 따라가면서 만들어지는 면(fragment)를 그리는 방식이고, 좌표는 반시계 방향 순서로 정의한다.

OpenGL ES에서는 (0, 0, 0)이 프레임의 중앙, (1, 1, 0)이 프레임의 오른쪽 위이다.

![\[그림 1\] 두 개의 삼각형으로 사각형 그리기](https://developer.android.com/images/opengl/ccw-square.png)

자세한 내용은 [공식 문서](https://developer.android.com/training/graphics/opengl/shapes)에서 확인할 수 있으며, 삼각형을 정의한는 코드는 아래와 같다

{% hint style="warning" %}
공식 예제 코드에서는 적당한 삼각형이지만, 아래 코드는 **정삼각형**으로 수정하였다.
{% endhint %}

```java
public class Triangle {

    // number of coordinates per vertex in this array
    static final int COORDS_PER_VERTEX = 3;
    static float triangleCoords[] = {
            // in counterclockwise order:
            0.0f, 0.577350269f, 0.0f,     // top
            -0.5f, -0.288675134f, 0.0f,   // bottom left
            0.5f, -0.288675134f, 0.0f     // bottom right
    };

    private final int vertexCount = triangleCoords.length / COORDS_PER_VERTEX;
    private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex
    
    float color[] = { 0.63671875f, 0.76953125f, 0.22265625f, 1.0f };
    
    public Triangle() {
        // initialize vertex byte buffer for shape coordinates
        ByteBuffer bb = ByteBuffer.allocateDirect(
            // (number of coordinate values * 4 bytes per float)
            triangleCoords.length * 4);
        
        // use the device hardware's native byte order
        bb.order(ByteOrder.nativeOrder());
        vertexBuffer = bb.asFloatBuffer();
        vertexBuffer.put(triangleCoords);
        vertexBuffer.position(0);
    }
}
```

도형을 그리는 데 필요한 것:

* Vertex Shader - 도형의 **꼭짓점을 렌더링**하는 **OpenGL ES 그래픽 코드**
* Fragment Shader - 색상 또는 질감으로 도형의 **면을 렌더링**하는 **OpenGL ES 코드**
* Program - 하나 이상의 도형을 그리는 데 사용할 **셰이더가 포함된 OpenGL ES 객체**

*Vertex Shader* 와 *Fragment Shader* 는 **코드**이다. 따라서 컴파일 할 GLSL(OpenGL Shading Language) 코드로 작성된다.

```java
public class Triangle {

    private final String vertexShaderCode =
        "attribute vec4 vPosition;" +
        "void main() {" +
        "  gl_Position = vPosition;" +
        "}";

    private final String fragmentShaderCode =
        "precision mediump float;" +
        "uniform vec4 vColor;" +
        "void main() {" +
        "  gl_FragColor = vColor;" +
        "}";

    ...
}
```

각 도형들이 *Program* 을 가지고 있고, 렌더러(Renderer)가 나중에 이 프로그램을 렌더링에 추가하여 객체를을 그릴 것이다.

SurfaceView에서 Renderer를 통하여 OpenGL ES 객체를 그리는 것이다.

두 셰이더(Shader)와 프로그램(Program), 그리고 렌더러(Renderer)까지 예제를 잘 따라했다면 삼각형이 그려지는 것을 볼 수 있다.

![\[그림 2\] 찌그러진 삼각형](https://developer.android.com/images/opengl/ogl-triangle.png)

그런데 삼각형이 찌그러져 있을 것이다. 분명 정삼각형을 정의했는데!

그 이유는 프로젝션(Projection)과 카메라 뷰(Camera View)가 없어서 그렇다.

![\[그림 3\] 정의한 도형의 좌표계와 실제 그려진 좌표계](https://lh5.googleusercontent.com/AR8xyMB8s_i4tT84QMJK27wxNZjFAPUn7NL3uRU-ApsvRHG2b2jW8g8P3rK2-CpL7su7NgBEUhXcmB2zZooJmUxa0LddhJHV9ITVy47eLVembsLXKDLkekKQ71XeFXha15OnhX0w)

\[그림 2]에서 우리가 그래픽을 그리는 `GLSurfaceView`는 \[그림 3]의 오른쪽처럼 뷰 포트의 크기가 변한다.

이것을 `GLSurfaceView`의 너비와 높이를 보고 다시 좌표를 조정해야하는데, **투영(Projection)**&#xC2DC;키기 위해서 **가상의 카메라 위치**를 잡아야한다.

```java
public class MyGLRenderer implements GLSurfaceView.Renderer {

    // vPMatrix is an abbreviation for "Model View Projection Matrix"
    private final float[] mMVPMatrix = new float[16];
    private final float[] mProjectionMatrix = new float[16];
    private final float[] mViewMatrix = new float[16];

    @Override
    public void onSurfaceChanged(GL10 unused, int width, int height) {
        // Adjust the viewport based on geometry changes
        GLES20.glViewport(0, 0, width, height);
        float ratio = (float) width / height;

        // this projection matrix is applied to object coordinates
        // in the onDrawFrame() method
        Matrix.frustumM(mProjectionMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
    }
    
    ...
}
```

\[그림 3]을 보면 좌표계는 \[-1.0, 1.0] 사이의 값으로 표현된다. 찌그러진 것을 복구하기 위해서 좌표의 아래(bottom)와 위(top)를 `-1`과 `1`로 했을 때, 왼쪽(left)과 오른쪽(right)의 비율만 조정하는 것이다.

![\[그림 4\] View Frustum (Image from WikiMedia)](https://upload.wikimedia.org/wikipedia/commons/0/02/ViewFrustum.svg)

위 그림에서 Near와 Far가 아주 가까워 착 붙어있는 뷰 포트에 투영시킨다고 생각하면 되겠다.

`Matrix.frustumM()` 함수는 그렇게 비율을 조정하는 행렬(Matrix)을 첫 번째 인자에 넣어준다. 우린 이 행렬(Matrix)로 객체를 투영시킬 수 있다.

![\[그림 5\] Apply projection and camera views](https://4141123541-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MGNgdThR1z0dmeYuF3Z%2F-MHH9dDXGei7unHYSWNV%2F-MHHY0uCGJ4iUJKH4RnV%2FOpenGL-Triangle-projection.png?alt=media\&token=58fb44f1-e4c5-41de-abde-8cebc4c6b18e)

```java
// MyGLRenderer.onDrawFrame()
@Override
public void onDrawFrame(GL10 unused) {
    // Draw background color
    GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
    // Set the camera position (View matrix)
    Matrix.setLookAtM(mViewMatrix, 0, 0, 0, -3, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
    // Calculate the projection and view transformation
    Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mViewMatrix, 0);
    // Draw triangle
    mTriangle.draw(mMVPMatrix);
}
```

가상의 카메라 뷰인 `mViewMatrix` 를 적절한 위치에 두고,  `onSurfaceChanged` 에서 만들어진 투영 행렬 `mProjectionMatrix` 과 합쳐서 그것으로 다시 삼각형을 그린다.&#x20;

![\[그림 6\] 카메라의 시점](https://4141123541-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MGNgdThR1z0dmeYuF3Z%2F-MHH9dDXGei7unHYSWNV%2F-MHHaZIlUkAex_EvHt5r%2Fimage.png?alt=media\&token=97b76e26-c82d-4140-9a3d-e1e17ee25e7a)

Matrix.setLookAtM 의 파라미터는 [레퍼런스](https://developer.android.com/reference/android/opengl/Matrix#setLookAtM\(float%5B%5D,%2520int,%2520float,%2520float,%2520float,%2520float,%2520float,%2520float,%2520float,%2520float,%2520float\))와 위 그림을 함께 보면 좋다.

## Code

<https://github.com/joonas-yoon/android-opengl-example/tree/142dcbab1815e6ec25f5954493a07c088f27552c>

## Links

* <https://developer.android.com/training/graphics/opengl/shapes>
* <https://developer.android.com/training/graphics/opengl/projection>
* [https://android.googlesource.com/platform/development/.../example/android/opengl](https://android.googlesource.com/platform/development/+/master/samples/OpenGL/HelloOpenGLES20/src/com/example/android/opengl)
* <https://developer.android.com/reference/android/opengl/Matrix>


# 삼각형 움직이기

Android에서 OpenGL ES 2.0으로 객체를 터치한 만큼 옮겨본다.

[이전에 그렸던 삼각형 객체](/graphics/opengl/draw-triangle)를 터치(Touch)한 만큼 옮겨볼 것이다.

{% hint style="warning" %}
[공식 문서와 예제](https://developer.android.com/training/graphics/opengl/motion)는 삼각형을 회전시키지만, 터치 움직임과 너무 맞지 않는 동작이다.

나는 삼각형을 **움직이도록** 수정하였다.
{% endhint %}

![\[그림 1\] Translate Triangle](https://4141123541-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MGNgdThR1z0dmeYuF3Z%2F-MHHcgrQNeD9gWKqLLoJ%2F-MHHitJoNXrQyKS0aNsF%2Fandroid-opengl-translate.gif?alt=media\&token=d91adf66-338b-45f6-82f5-74e30ec108ae)

기존 코드에서 변경되는 부분은 거의 없다. 렌더러(Renderder)에서 위치를 이동시키는 행렬을 하나 더 추가하고 그것을 카메라 뷰와 합치면 된다.

먼저 터치 이벤트를 등록해야한다.

```java
public class MyGLSurfaceView extends GLSurfaceView {
    ...

    private float mPreviousX;
    private float mPreviousY;

    @Override
    public boolean onTouchEvent(MotionEvent e) {
        // MotionEvent reports input details from the touch screen
        // and other input controls. In this case, you are only
        // interested in events where the touch position changed.
        float x = e.getX();
        float y = e.getY();
        switch (e.getAction()) {
            case MotionEvent.ACTION_MOVE:
                float dx = mPreviousX - x;
                float dy = mPreviousY - y;
                mRenderer.translate(dx, dy, 0f); // translate
                requestRender();
        }
        mPreviousX = x;
        mPreviousY = y;
        return true;
    }
}
```

터치 이벤트가 발생할 때마다 이동한 만큼 변위값을 렌더러에 넘겨준다.

```java
public class MyGLRenderer implements GLSurfaceView.Renderer {
    ...
    private final float[] mTranslateMatrix = new float[16];
    private int screenHeight;
    private int screenWidth;

    @Override
    public void onSurfaceCreated(GL10 unused, EGLConfig config) {
        ...
        Matrix.setIdentityM(mTranslateMatrix, 0);
    }

    @Override
    public void onSurfaceChanged(GL10 unused, int width, int height) {
        this.screenWidth = width;
        this.screenHeight = height;
        ...
    }

    @Override
    public void onDrawFrame(GL10 unused) {
        float[] scratch = new float[16];
        
        ...
        
        // Combine the translated matrix with the projection and camera view
        Matrix.multiplyMM(scratch, 0, mMVPMatrix, 0, mTranslateMatrix, 0);

        // Draw triangle
        mTriangle.draw(scratch);
    }

    public void translate(float dx, float dy, float dz) {
        Matrix.translateM(mTranslateMatrix, 0,
            dx * 2f / screenHeight,
            dy * 2f / screenHeight,
            dz * 2f / screenHeight);
    }
}
```

`...` 부분은 기존의 코드와 동일하다.

터치의 변위값, 즉 드래그한 만큼 `mTranslateMatrix` 에 반영한다. 그리고 `onDrawFrame()` 에서, 이동을 위해 추가한 행렬 `mTranslateMatrix` 도 결합시켜 그리도록 한다.

주의할 점은,  `mTranslateMatrix` 의 최초 상태를 단위 행렬(Identity Matrix)로 만들어주고 시작해야한다.

추가로 살필 수 있는 부분으로는, `dx * 2f / screenHeight` 라는 수식이다.&#x20;

터치의 변위값은 화면의 좌표계를 사용했다. 화면 비율에 맡게 가로/세로값으로 좌표계를 수정했었으니 그에 맞게 다시 변환해준 것이다.&#x20;

## Code

<https://github.com/joonas-yoon/android-opengl-example/tree/924058f8eb8fbbf566ffccc8e72c0cd308f800d6>

## Links

* <https://developer.android.com/training/graphics/opengl/motion>
* [https://android.googlesource.com/platform/development/.../example/android/opengl](https://android.googlesource.com/platform/development/+/master/samples/OpenGL/HelloOpenGLES20/src/com/example/android/opengl)


# 다각형 그리기

Android에서 OpenGL ES 2.0으로 다각형을 정의하고 그려본다.

삼각형에서는 좌표들(coords) 배열을 9개(3차원 점 \* 3개의 점)만 정의했으니,  더 많은 좌표를 추가하면 다각형이 될 것이다.

하지만 그리는 것은 다르다.

OpenGL에서는 그리는 모드에 따라 각 점을 해석하는 방식이 다르다.

![그림 1. OpenGL Drawing Primitives](https://4141123541-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MGNgdThR1z0dmeYuF3Z%2F-MS5t5WwQ1gCXp_69LLB%2F-MS5vE4H5CRlsoTqMb-o%2Fimage.png?alt=media\&token=ffe5cd51-bfd2-4817-8899-41a9c005f504)

[이전에 삼각형을 그릴 때](/graphics/opengl/draw-triangle) 사용한 모드는 `GL_TRIANGLES` 이다.\
3개씩 묶어서 만들어지는 삼각형 면을 채우는 방식이다.

이 모드로 사각형을 그린다면, 점의 순서를 아래와 같이 바꿔야한다.

![그림 2. 두 개의 삼각형을 사용하여 사각형 그리기](https://4141123541-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MGNgdThR1z0dmeYuF3Z%2F-MS5t5WwQ1gCXp_69LLB%2F-MS5u2pTWmkENoY9SmPC%2Fimage.png?alt=media\&token=fdb382fe-27da-4ddd-8e8e-f386994d6851)

(x1, y1, z1)를 1번 정점이라고 하면, 사각형을 그리기 위해서는 (1번-2번-3번)의 삼각형 하나와, (1번, 3번, 4번)의 삼각형, 총 2개의 삼각형을 그려야한다.

여기에는 방법이 다시 2가지가 있는데, 하나는 `coords[]` 배열에 점을 3개씩 끊어서 순서대로 직접 모두 저장하거나, 점의 순서를 저장해두고 나중에 렌더링할 때 그리는 순서를 결정하는 방법이 있다.

순서대로 직접 모두 저장하는 방식은 아래와 같다.

```java
static float squareCoords[] = {
   x1, y1, z1,
   x2, y2, z2,
   x3, y3, z3, // 여기서 삼각형 하나 완성
   x1, y1, z1,
   x3, y3, z3,
   x4, y4, z4  // 두번째 삼각형 완성
};
```

점의 순서를 지정하는 코드는 아래와 같다.

```java
public class Square {

    private FloatBuffer vertexBuffer;
    private ShortBuffer drawListBuffer; // drawListBuffer에 점의 순서를 저장한다.
    
    static float squareCoords[]; // 생략
    private short drawOrder[] = { 0, 1, 2, 0, 2, 3 }; // order to draw vertices

    public Square() {
        // ...
        
        // initialize byte buffer for the draw list
        ByteBuffer dlb = ByteBuffer.allocateDirect(
                drawOrder.length * 2); // short은 2바이트라서 2를 곱한다.
        dlb.order(ByteOrder.nativeOrder());
        drawListBuffer = dlb.asShortBuffer();
        drawListBuffer.put(drawOrder);
        drawListBuffer.position(0);
    }
    
    // Renderer에서 호출하면 그린다.
    public void draw(float[] mvpMatrix) {
    
        GLES20.glUseProgram(mProgram);
        
        // ...
        
        // glDrawArrays 대신 아래와 같이 바꾼다.
        GLES20.glDrawElements(
                GLES20.GL_TRIANGLES, drawOrder.length,
                GLES20.GL_UNSIGNED_SHORT, drawListBuffer);
        
        // ...
    }
}
```

Renderer에서 호출되는 부분은 아래와 같다.

```java
@Override
public void onDrawFrame(GL10 unused) {
    // ...
    // 카메라 에 맞춰 적절히 projection된 매트릭스가 넘어간다.
    mShape.draw(mMVPMatrix);
}
```

## Links

* (figure 1) <https://developer.android.com/training/graphics/opengl/shapes>
* (figure 2) <http://cse.csusb.edu/tongyu/courses/cs420/notes/drawing.php>


# 정사면체 그리기

Android에서 OpenGL ES 2.0으로 "알록달록한" 정사면체를 정의한다.

[삼각형](/graphics/opengl/draw-triangle)도 그려봤고, [사각형](/graphics/opengl/draw-polygon)도 그려봤으니, 이제 정사면체를 그려보자.\
당연히 점의 좌표를 더 추가하면 된다.

결과부터 보자면, 각 점의 좌표와 순서는 아래와 같다.

```java
float[] coords = {
    0f/6f, 0f/6f, 3.464101f/6f,           // A(0, 0, 2*sqrt(3))
    0f/6f, 3.464101f/6f, -1.732050f/6f,   // B(0, 2*sqrt(3), -sqrt(3))
    -3f/6f, -1.732050f/6f, -1.732050f/6f, // C(-3, -sqrt(3), -sqrt(3))
    3f/6f, -1.732050f/6f, -1.732050f/6f   // D(3, -sqrt(3), -sqrt(3))
};
final short[] drawOrder = { // order to draw vertices
    0, 1, 2,  // ABC
    0, 2, 3,  // ACD
    0, 3, 1,  // ADB
    1, 3, 2   // BDC
};
```

한 변의 길이를 1로 만드는 계산식을 위해, 반지름이 15인 구를 외심으로 하는 정사면체를 구할 것이다.

정사면체의 꼭짓점 중 z축 위에 있는 점을 A, 그리고 그 아래로 삼각형을 이루는 점을 B, C, D 라고 하자. 그리고 이 정사면체의 중심 O를 (0, 0, 0)으로 정의할 것이다.

![](https://4141123541-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MGNgdThR1z0dmeYuF3Z%2F-MSbWoukh4kRYXTqarQg%2F-MSch7tjqoyDFBMET6ze%2Fimage.png?alt=media\&token=3dc8dfa8-7466-4359-8cf6-f812681f320c)

{% hint style="warning" %}
OpenGL 에서는 화면을 xy평면으로 하기 때문에, 일반적으로 생각하는 유클리디안 공간 좌표에서 그리는 모양과 다르게 나오는 것에 주의해야 한다.

도형은 [https://www.math3d.org/uspfpBIY](https://www.math3d.org/6dmIC93E) 에서 확인할 수 있다.
{% endhint %}

정사면체의 중심은 무게 중심이므로 AO:OH = 2:1 이다. 이 점을 이용해서 각 점을 구하면 아래와 같다.

$$
\begin{cases}
~~A~~(0, 0, 3\sqrt{3}) \\
~~B~~(0, 2\sqrt{3}, -\sqrt{3}) \\
~~C~~(-3, -\sqrt{3}, -\sqrt{3}) \\
~~D~~(3, -\sqrt{3}, -\sqrt{3})
\end{cases}
$$

변CD 만 봐도 길이가 6인 것을 알 수 있다. 한 변의 길이가 1이 될 수 있도록 전부 6으로 나누면, 한 변의 길이가 1인 정사면체의 좌표가 된다.

{% hint style="info" %}
다른 방법으로는, model matrix를 1/6 만큼 스칼라곱 스케일링(scaling)하는 방법도 있다.
{% endhint %}

`drawOrder` 는 각 면들이 바깥쪽을 앞면으로 할 수 있도록 반시계 방향으로 정의한 것이다. (이후에 Face culling 단계에서 이 부분이 적용된다)

{% hint style="info" %}
3차원 primitive의 정점들(vertices)이 잘 정의되었고 그려지고 있는 지 확인하기 위해서,\
터치 이벤트에 원래 튜토리얼에 제공되는 "회전(rotate)" 매트릭스 연산을를 사용하였다.
{% endhint %}

![](https://4141123541-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MGNgdThR1z0dmeYuF3Z%2F-MSbWoukh4kRYXTqarQg%2F-MScaLZJCrMnRJB9l38S%2FfragTetrahedron.gif?alt=media\&token=bf1cfad4-08b0-44f4-9b4e-9a1ae4ca7055)

정사면체의 정점들은 잘 구성되었지만, 입체감이 전혀 없어서 "추측 하기에" 정사면체이다.

이는 fragment shader에서 ***uniform*** 변수인 `vColor` 를 통해 같은 색상을 계속 그리기 때문이다. 정점의 위치에 상관없이 화면에 보이는 (투영되는) 지점에는 항상 `vColor` 가 찍히고 있는 것이다.

각 정점들(vertices)마다 색상을 지정해서 그려지도록 바꿔보자.

## 색 입히기

```java
final int COLORS_PER_VERTEX = 4; // r, g, b, alpha 총 4개
float colors[] = {
    1f, 1f, 1f, 1f, // 점 A의 색상 rgba(1, 1, 1, 1)
    1f, 0f, 0f, 1f, // 점 B
    0f, 1f, 0f, 1f, // 점 C
    0f, 0f, 1f, 1f, // 점 D
};

public void init() {
    // vertexBuffer를 생성하던 그 부분
    // ...
    
    // 각 vectex의 color를 attribute로 넘겨주기 위해 버퍼의 형태로 만든다.
    colorBuffer = ByteBuffer.allocateDirect(colors.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
    colorBuffer.put(colors);
    colorBuffer.position(0);
    
    // ...
}

public void draw(float[] mvpMatrix) {
    // ...

    // vertex shader에서 aColor 라는 attribute 변수의 핸들을 가져온다.
    mColorHandle = GLES20.glGetAttribLocation(mProgram, "aColor");
    GLES20.glVertexAttribPointer(mColorHandle, COLORS_PER_VERTEX,
            GLES20.GL_FLOAT, false,
            4 * COLORS_PER_VERTEX, colorBuffer);
    
    // ...
}
```

쉐이더 코드인 GLSL은 아래와 같이 바뀐다.

```java
protected final String vertexShaderCode =
    "uniform mat4 uMVPMatrix;" +
    "attribute vec4 vPosition;" +
    "attribute vec4 aColor;" +  // 각 vertex에 있을 색상(rgba)값 버퍼
    "varying vec4 ourColor;" +  // fragement shader로 넘어갈 보간 값
    "void main() {" +
    "    gl_Position = uMVPMatrix * vPosition;" +
    "    ourColor = aColor;" +
    "}";
protected final String fragmentShaderCode =
    "precision mediump float;" +
    "varying vec4 ourColor;" +  // 넘겨받은 보간 값
    "void main() {" +
    "    gl_FragColor = ourColor;" +
    "}";
```

![](https://4141123541-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MGNgdThR1z0dmeYuF3Z%2F-MSbWoukh4kRYXTqarQg%2F-MScmcXNBlQWUBBvAlAu%2FcoloredTetrahedron.gif?alt=media\&token=7d58a332-2e9a-4dca-9550-6771726398e0)

이것은 ***attribute*** 타입 때문에 가능하다. vertex shader에서 정점별로 다른 값(적절히 보간(interpolation)된 값)을 가지고 있다. 값이 보간되는 방식은, 정점과 정점 사이에 (vector로 인해) 선이 그려지는 원리를 생각해보면 된다.

이것을 다시 ***varying*** 타입의 변수를 통 fragment shader로 전달하면 그 위치에 맞는 색상이 그려질 수 있는 것이다.

{% hint style="success" %}
vertex shader에서는 값을 읽고 쓰는 것이 모두 가능하지만,\
fragment shader에서는 값을 읽는 것만 가능하다.
{% endhint %}

그리고 주의해야 할 점으로는, 몇 가지 설정을 추가해야 "의도대로" 동작한다.

```java
// 앞면/뒷면을 구분하는 Face culling을 활성화해야 우리가 의도한대로 렌더링된다.
// 기본적으로 반시계방향(CCW; Counter-Clock-Wise)이 앞면이고, 변경 가능하다.
GLES20.glEnable(GLES20.GL_CULL_FACE);

// vectex에서 해당 핸들러를 활성화해야 색상이 그려진다.
GLES20.glEnableVertexAttribArray(mColorHandle);
```

## Code

<https://github.com/joonas-yoon/android-opengl-example/tree/ee6a4ae569bddeab6033c71115c16e18a1d983a2>

## Refs

* <http://wanochoi.com/?p=1114>
* <https://learnopengl.com/Advanced-OpenGL/Face-culling>
* <http://www.opengl-tutorial.org/kr/beginners-tutorials/tutorial-4-a-colored-cube/>


# \[WIP] 마인크래프트 블럭 만들기

정육면체에 텍스처(Texture)를 입혀서 마인크래프트 블럭처럼 보이도록 만든다.

![](https://4141123541-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MGNgdThR1z0dmeYuF3Z%2F-MSgjTi9aiwKEYjJfQFI%2F-MShGeAgkueOD9T1McVu%2Fcube.gif?alt=media\&token=d507cc41-5021-442a-8488-2b4fb2d452b4)

![](https://4141123541-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MGNgdThR1z0dmeYuF3Z%2F-MSgjTi9aiwKEYjJfQFI%2F-MShGfZKfHFvYkw3yUjK%2Fcube2.gif?alt=media\&token=a027b6e0-7c0f-44f4-a0e8-6159f81f7a4c)

![grass texture map](https://4141123541-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MGNgdThR1z0dmeYuF3Z%2F-MSgjTi9aiwKEYjJfQFI%2F-MShGSzNK4rb4oBbyyjQ%2Fimage.png?alt=media\&token=b2f162e3-6a98-4253-89e9-644478209b8d)

![](https://4141123541-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MGNgdThR1z0dmeYuF3Z%2F-MSgjTi9aiwKEYjJfQFI%2F-MShGhqPRxgoM7abnZB2%2Fmccube.gif?alt=media\&token=477694c3-558a-4c68-9f4d-f37741a365eb)

![](https://4141123541-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MGNgdThR1z0dmeYuF3Z%2F-MSgjTi9aiwKEYjJfQFI%2F-MShJrQdKPIbEtFp8WiP%2Fuvmap.png?alt=media\&token=e78502c7-3772-430c-8aeb-f48e1e8d20a8)

```

float textureCoords[] = {
    // (u, v), (u, v), (u, v) for a traingle
    0.0f, 0.3f, 0.0f, 0.6f, 1.0f, 0.6f, /* front  1st triangle */
    0.0f, 0.3f, 1.0f, 0.6f, 1.0f, 0.3f, /* front  2nd triangle */
    0.0f, 0.3f, 0.0f, 0.6f, 1.0f, 0.6f, /* right  1st triangle */
    0.0f, 0.3f, 1.0f, 0.6f, 1.0f, 0.3f, /* right  2nd triangle */
    0.0f, 0.3f, 0.0f, 0.6f, 1.0f, 0.6f, /* back   1st triangle */
    0.0f, 0.3f, 1.0f, 0.6f, 1.0f, 0.3f, /* back   2nd triangle */
    0.0f, 0.3f, 0.0f, 0.6f, 1.0f, 0.6f, /* left   1st triangle */
    0.0f, 0.3f, 1.0f, 0.6f, 1.0f, 0.3f, /* left   2nd triangle */
    0.0f, 0.0f, 0.0f, 0.3f, 1.0f, 0.3f, /* top    1st triangle */
    0.0f, 0.0f, 1.0f, 0.3f, 1.0f, 0.0f, /* top    2nd triangle */
    0.0f, 0.6f, 0.0f, 1.0f, 1.0f, 1.0f, /* bottom 1st triangle */
    0.0f, 0.6f, 1.0f, 1.0f, 1.0f, 0.6f, /* bottom 2nd triangle */
};
```

## Links

* <http://www.opengl-tutorial.org/kr/beginners-tutorials/tutorial-5-a-textured-cube/>
* <https://blog.daum.net/aero2k/84>
* <https://www.learnopengles.com/android-lesson-four-introducing-basic-texturing/>


# \[WIP] Lighting, Normal Mapping

노멀맵과 빛, 광원(Light)

## Normal Mapping

노멀맵과 라이트

노멀맵을 대충 만들어주는 사이트: <http://www.smart-page.net/smartnormal/>

두 종류

* Model/Object Space Normal Map
* Tangent Space Normal Map

## Light

빛의 유형

* point light – 한 점에서 광원이 전체로 뻗는 형태. 각 버텍스마다 다른 빛의 방향을 가진다. (전구, 횃불 등)
* spot light – 한 점에서 콘(cone) 형태로 정해진 각도만큼 밝힘.
* directional light – 위치는 없고 방향만 있는 빛. 모든 정점에 동일한 세기와 각도로 도착한다. (태양광)

Lambertian reflectance

Phong shading


# Internet

Internet에서 일어나는 이야기


# iOS/Safari

iOS/Safari 관련 내용들 모음

## No Support WebM

**iOS는** 동영상 포맷 중 **.webm 확장자를 지원하지 않는다.**

## User Agents

iOS 위에서 돌아가는 앱들의 유명한 User Agent는 [이 사이트](https://developers.whatismybrowser.com/useragents/explore/operating_system_name/ios/)에서 확인할 수 있다.

예전에는 iP(ad|hone|od) 같은 정규식으로 처리됐지만, iPad가 버전 13을 넘어가면서 이게 또 바뀌었다..\
[이 글](https://littleshark.tistory.com/56)의 작성자와 완전히 동일한 경험을 겪어서 링크로 대체한다.

## Links

* <https://developers.whatismybrowser.com/useragents/explore/operating_system_name/ios/>
* [iPadOS 13 에서 userAgent가 Mac으로 넘어오는 이슈](https://littleshark.tistory.com/56)


# Javascript


# async, defer 속성

## async

`<script async>...</script>` 는 자주 봤다. 이름에서 알 수 있듯이 비동기적으로 실행된다.

페이지 로드와 다른 스크립트의 로드를 기다리기 않고 바로 실행한다. 그렇기때문에 `DOMContentLoaded` 이벤트와 싱크가 안 맞을 수 있다.

## defer

defer 속성은 이름으로 추측해본다면, '미루다/지연하다'는 의미를 내포하고 있다.

이 속성을 가진 스크립트는 백그라운드에서 다운로드되고, 그 동안 HTML 파싱은 멈추지 않는다.\
그리고 `DOMContentLoaded` 이벤트는 DOM이 준비되어도, 이 `defer` 스크립트를 기다린다고 한다.

요약하자면, DOM이 준비되고, `defer` 스크립트가 (다운로드 완료와 상관없이) 적힌 순서대로 실행된 이후에, 나머지 스크립트들이 실행된다는 뜻이다. (`async`는 당연히 논외)

아래 페이지에서 자세한 설명과 실행 결과를 확인할볼 수 있다.

{% embed url="<https://ko.javascript.info/script-async-defer>" %}


# 나머지 매개변수 (Rest parameter)

C언어에서 가변인자와 같은 기능이다. 문법은 다음과 같다.

```javascript
function f(...args) {
  // ...
}
```

이것 역시 ES6 부터 추가되었으며, 사용을 권장하고 있다.

이전에도 `arguments` 라는 예약어가 있었으나, 이건 파라미터 전부에 접근한다. (쉘에서 `$1` 의 표현을 떠올리면 이해하기 쉽다.)

그럼 얘는 뭐가 다르냐?

`arguments`는 따로 정의된 특수한 객체지만, 나머지 매개변수는 `Array`의 인스턴스이다.

그리고 `...<name>` 에 해당하는 부분만 `Array`로 넘어간다.

```javascript
function f(tag, ...msg) {
   return tag +": " + msg.join('/');
}

f('alphabet', 'a', 'b', 'c');  // "alphabet: a/b/c"
f('primes', 2, 7, 9, 11);      // "primes: 2/7/9/11"
```

그렇다보니 `...arr` 로 넘기고 `arr.pop()` 과 같은 조작이 가능하다.

## Links

* <https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Functions/rest_parameters>


# 화살표 함수 표현 (arrow function expression)

## 화살표 함수 표현

자바스크립트로 서버사이드, 클라이언트사이드 모두 개발하면서 `() => {}` 이나 `() => x` 와 같은 문법을 많이 사용했었다.

그 이유는 그 많던 이름없는 `function` 키워드가 사라졌고, 코드가 짧아 읽기에도 편했기 때문이다.

하지만 그 문법과 동작을 자세히 살펴본 적이 없었다.

화살표 함수는 ES6에서 추가된 문법이다. 화살표 함수는 항상 이름이 없는 "익명"이다.

```javascript
(param1, param2, …, paramN) => { statements }
(param1, param2, …, paramN) => expression
// 다음과 동일함:  => { return expression; }

// 매개변수가 하나뿐인 경우 괄호는 선택사항:
(singleParam) => { statements }
singleParam => { statements }

// 매개변수가 없는 함수는 괄호가 필요:
() => { statements }
```

화살표(`=>`)다음의 괄호의 유무는 실행만 하는 지와 값을 반환하는 지 여부의 차이이다.

함수 정의를 어떻게 표현하는 지의 대한 문법이므로, 아래 실행 결과로 확인해봤다.

```javascript
> function f(){
  console.log(1);
  return 2;
}

> let y = () => { f() }
> let z = () => f()

> y()
// output: 1
// return: undefined

> z()
// output: 1
// return: 2
```

한가지 또 알게 된 사실은, [이 문서](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Functions/%EC%95%A0%EB%A1%9C%EC%9A%B0_%ED%8E%91%EC%85%98)에 따르면 `this`을 바인딩이 되지 않는다고 말한다. 즉, `this` 가 없다.

그럼 이 익명 함수에서 사용하는 `this`는 대체 무엇을 가리킬까.

```javascript
> function A() {
  this.count = 0;
  setInterval(() => {this.count++;}, 1000);
}

> let a = new A()
// a = A {count: 1}
// a의 카운트는 1초마다 증가하고 있다. 이는 객체마다 독립적이다.
```

자신을 둘러싸고 있는 렉시컬 스코프(Lexical scope)의 `this`를 사용한다. 즉, 이 함수가 선언된 스코프의 `this` 를 사용한다.

위 코드는 `A` 라는 클래스를 만들어서 변수를 추가하였고, 화살표 함수로 count 변수를 1초마다 1씩 증가하게 한다.

(사실 자바스크립트의 모든 클래스 생성자는 function 이기 때문에...)

##

Links

* <https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Functions/%EC%95%A0%EB%A1%9C%EC%9A%B0_%ED%8E%91%EC%85%98>


# Template Literals

[Template Literals](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Template_literals)는 다른 언어에도 존재하여 익숙할 문자열 포맷팅 문법이다. 예를 들면, `printf("%s")` 나  `print("{} and {}")` 과 같다.

그 사용 방법은 아래와 같다.

```javascript
let datatime = (new Date()).toDateString();
console.log(`<div class="date">${datetime}</div>`);
```

문제는 마음껏 사용할 수 없는 문법이라는 점이다.

[ES6 호환성 표](https://kangax.github.io/compat-table/es6/)를 보면 Internet Explorer 11 버전에서조차 이것을 지원하지 않는다.&#x20;

babel 트랜스컴파일러에서는 이것을 변환하는 플러그인 [@babel/plugin-transform-template-literals](https://babeljs.io/docs/en/babel-plugin-transform-template-literals)을 지원한다.


# TDZ (Temporal Dead Zone)

먼저 아래 코드를 살펴보자.

```javascript
new Car('red'); // Does it work? (no; ReferenceError)

class Car {
  constructor(color) {
    this.color = color;
  }
}
```

나는 당연히 클래스도 호이스팅되어 사용이 가능할 줄 알았으나, 아니었다. 참고로 함수는 가능하다.

```javascript
greet('World'); // Does it work? (yes)

function greet(who) {
  return `Hello, ${who}!`;
}
```

생각해보면 [ES6부터는 항상 strict 모드](https://262.ecma-international.org/6.0/#sec-strict-mode-code)로 해석되기 때문에 예상 가능한 결과였다.

`let`, `const`, `class` 키워드는 TDZ의 영향을 받으므로 주의해야겠다.

## &#x20;Links

{% embed url="<https://dmitripavlutin.com/javascript-variables-and-temporal-dead-zone/>" %}


# Spread syntax (...)

\[...a] 와 같은 연산자에 대해서

{% embed url="<https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax>" %}

ES6부터 생긴 문법으로, 코드를 작성할때나 어떤 솔루션들에서 종종 보인다.

처음 접했던 케이스는 가장 짧고도 간단한 array deep copy 였다. 자바스크립트는 객체를 대입할 때 copy-by-reference로 하기 때문에, 아래의 코드는 주석과 같은 결과가 나온다.

```javascript
let a = [1, 2, 3];
let b = a;
b[0] = 'x';
// a: ['x', 2, 3]
// b: ['x', 2, 3]
```

Spread syntax를 사용하면 아래와 같이 간단한 식으로 deep copy가 된다.

```javascript
let a = [1, 2, 3];
let b = [...a];
b[0] = 'x';
// a: [1, 2, 3]
// b: ['x', 2, 3]
```

ES9부터는 Object도 가능하다고 한다.

```javascript
let a = {x: 1, y: 2};
let b = {...a};
b['x'] = 9;
// a: {x: 1, y: 2}
// b: {x: 9, y: 2}
```


# Network

Computer Network에 대한 공부


# OSI 7 계층 모델

7 layers of the OSI Model

이 모델은 **프로토콜을 기능별로 나눈 것**이다. 각 레이어(Layer)는 하위 레이어의 기능을 사용할 수 있다.

레이어를 나눈 이유는 **통신의 과정을 단계별로 확인할 수 있기 때문**이다.

인터넷 연결이 안될 때 단계별로 어디가 문제인지 파악하는 모습을 떠올리면 된다.

![](https://www.coengoedegebure.com/content/images/2018/09/osimodel-3.gif)

상위 4개 레이어는 *Host Layer* 라고 불리고, 하위 3개 레이어는 *Media Layer* 라고 불린다.

## 7. **Application**

> High-level API's, including resource sharing, remote file access

컴퓨터에서 실행되는 소프트웨어와 네트워크 프로토콜 사이의 인터페이스를 제공한다.

원격 파일 접근, 원격 프린트, 가상 터미널 등 사용자와 가장 닿아있는 영역이다. 응용 서비스나 프로세스가 이 레이어에서 동작한다.

이 레이어에서 사용하는 프로토콜은 [HTTP](https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol), [FTP](https://en.wikipedia.org/wiki/File_Transfer_Protocol), [SMTP](https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol), [DNS](https://en.wikipedia.org/wiki/Domain_Name_System), [Telnet](https://en.wikipedia.org/wiki/Telnet), [SSH](https://en.wikipedia.org/wiki/Secure_Shell), [IMAP](https://en.wikipedia.org/wiki/Internet_Message_Access_Protocol), [POP](https://en.wikipedia.org/wiki/Post_Office_Protocol), [SNMP](https://en.wikipedia.org/wiki/Simple_Network_Management_Protocol) 등이 있다.

## 6. **Presentation**

Translation of data between a networking service and an application. This is the layer where character encoding, data compression and encryption takes place.

## 5. **Session**

Managing communication sessions.

## 4. **Transport**

Reliable transmission of data segments between nodes on a network, including segmentation, acknowledgment and multiplexing.

## 3. **Network**

Structuring and managing a multi-node network, including addressing, routing and traffic control.

## 2. **Data Link**

Reliable transmission of data frames between two nodes connected by the physical layer.

## 1. **Physical**

Transmission and reception of raw bit streams over a physical medium.

## Links

* <https://en.wikipedia.org/wiki/OSI_model>
* [https://ko.wikipedia.org/wiki/OSI\_모형](https://ko.wikipedia.org/wiki/OSI_%EB%AA%A8%ED%98%95)
* <https://www.coengoedegebure.com/osi-model/>
* <https://mycodecamp.blogspot.com/2019/10/7-layer-of-osi-model.html>
* 응용 서비스나 프로세스가 바로 응용계층에서 동작


# Uncategorized

분류되지 않은 글


# 2021/12/07

## SQLite on Heroku

{% embed url="<https://devcenter.heroku.com/articles/sqlite3>" %}

간단한 Python/Flask 앱을 배포하려고 했는데, 계속 500 에러가 났다. 로컬에서는 잘 되는데, Heroku에서의 환경이 의심되었고 정말 한 줄씩 원인을 찾았다.

production 환경에 적합하지 않다는 이유로, Heroku에서 지원하지 않고 PostgreSQL를 제공하고 있다.


# 2020/09/03

## DNS와 Nameserver

### Context

기존에 AWS Route53에 등록한 도메인을 그대로 사용하면서, 연결된 IP 주소는 GCP(Google Cloud Platform)의 VM인스턴스로 연결하려 하였다.

GCP의 VM 인스턴스는 외부 고정 IP로 설정하였다. 하지만, IP 주소로는 접속이 되지만 도메인으로는 접속되지 않는 현상이 발생했다.

레코드(record) 세트도 모두 잘 설정되어 있었다.

### TIL

문제는 등록된 도메인의 네임서버(nameserver)가 잘못되어 있었다.

Route53에서 네임서버를 수정하였지만, 계속 반영이 안 되었다가 약 3\~4시간이 지나고는 잘 되는 것을 확인하였다.

업데이트에 최소 2시간부터 48시간까지도 걸린다고하니, 조급하게 확인하지 않도록 하자.


# 2020/09/04

## Github Actions

### HEAD\~n

Github Actions에서 `actions/checkout@v2`는 최근 1개의 히스토리만 가져온다. 그래서 `HEAD~1` 등을 하면 해당 리비전(revision)을 찾을 수 없다고 나온다.

이를 해결하기 위해서는 `fetch-depth` 옵션을 추가해야한다. [#](https://github.com/actions/checkout#checkout-head)

```
- uses: actions/checkout@v2
  with:
    fetch-depth: 2
- run: git checkout HEAD^
```

### Error with exit code 1.

분명 문제 없이 프로세스를 끝냈지만, 아무 로그도 없이 빌드에 실패했다고 나오는 경우가 있다.

yml 파일과 그 결과는 이랬다.

```
- run: MSG=`git diff HEAD HEAD~1 --name-only | egrep [0-9]+\/`
```

```
##[error]Process completed with exit code 1.
```

이것과 관련해서는 아래 링크로 해결할 수 있다.

<https://github.com/actions/virtual-environments/issues/1279#issuecomment-663462004>

3가지 케이스로 실험했는데 결과는 다음과 같다.

1. `./test.sh`로 실행 (성공)
2. `source ./test.sh`로 실행 **(실패)**
3. `source ./test.sh || true`로 실행 **(성공)**

이 사례로 보아, 쉘에서 스크립트를 직접 실행하면 exit code가 0이 아닌 것으로 보인다. (`source`로만 실행 시 실패인 것을 참고)


# 2020/08/22

## GCP: docker machine

**GCP(Google Cloud Platform)의 Compute Engine 인스턴스에서는 가상화 컨테이너 기능을 지원하지 않는다.**

그래서 VirtualBox 같은 하이퍼바이저를 설치해도, 도커 머신의 드라이버로 만들 수 없다. (Hosted 형태의 컨테이너가 안된다는 뜻)

아무리 이미지를 바꿔도 계속 만났던 오류 메시지:

```
joonas_yoon@gce-instance:~$ docker-machine create --driver virtualbox vm00
Running pre-create checks...
Error with pre-create check: "This computer doesn't have VT-X/AMD-v enabled. Enabling it in the BIOS is mandatory"
```

진짜 계속 찾아봤는데, 전부 안됐다.

<https://github.com/docker/machine/issues/4590>

위 이슈 쓰레드에서 해결됐다는 코멘트들 다 안됐다. 인스턴스가 E2 유형이건 아니건 그냥 다 안됐다. N1 타입도 해봤고, Linux 16.04 LTS도 해보고 마켓에 있는 이미지도 해봤다.

![](https://www.macadamian.com/wp-content/uploads/2017/01/1-KO-vKqNqGk_8feDcpePvQQ.png)

* Create machines on Google Compute Engine - <https://docs.docker.com/machine/drivers/gce/>

드라이버를 `google`에 맞게 설정해줘서 해결했다. `key.json`은 프로젝트에서 사용하던 credentials.json 이다.

```
joonas_yoon@gce-instance:~$ export GOOGLE_APPLICATION_CREDENTIALS=$HOME/key.json
joonas_yoon@gce-instance:~$ gcloud auth login
joonas_yoon@gce-instance:~$ docker-machine create --driver google --google-project <GOOGLE_PROJECT_ID> vm01
Running pre-create checks...
(vm01) Check that the project exists
(vm01) Check if the instance already exists
Creating machine...
(vm01) Generating SSH Key
(vm01) Creating host...
(vm01) Opening firewall ports
(vm01) Creating instance
(vm01) Waiting for Instance
(vm01) Uploading SSH Key
Waiting for machine to be running, this may take a few minutes...
Detecting operating system of created instance...
Waiting for SSH to be available...
Detecting the provisioner...
Provisioning with ubuntu(systemd)...
Installing Docker...
Copying certs to the local machine directory...
Copying certs to the remote machine...
Setting Docker configuration on the remote daemon...
Checking connection to Docker...
Docker is up and running!
To see how to connect your Docker Client to the Docker Engine running on this virtual machine, run: docker-machine env vm01
```


# git/VCS

git & GitHub & VCS(Version Control System)


# Merge 커밋 메시지 수정

이미 commit한 메시지들은 `-i`, `--interactive` 옵션으로 수정할 수있다.

이 옵션으로 커밋의 위치 변경과 squash 까지 할 수 있다.

하지만 merge commit (`merge branch 'master' into 'dev'` 등) 은 보이지 않아 커밋 메시지를 수정하기가 어렵다.

{% embed url="<https://stackoverflow.com/questions/9930637/edit-a-merge-commit-with-git-rebase>" %}

`git rebase -i --rebase-merges HEAD~` 처럼 `--rebase-merges` 옵션을 붙이면 된다.

(git v2.22 이전이라면 --preserve-merges 옵션)


