반응형

Attribute Directives (링크)

 

DOM element의 모습 및 행동을 변경합니다.

오른쪽은 예제 입니다. Attribute Directive example / download example.

 

Attribute directives는 element의 속성으로 사용됩니다.  Template Syntax guide의 built-in NgStyle directive입니다. 예제로써, 동시에 여러 element를 변경할 수 있습니다.

 

1. foo.directive.ts에 selector는 ['appFoo'] 와같이 []로 감쌉니다.

2. HTML에 <p appFoo class"example"> 예제 </p> 와같이 사용합니다.

Directives 기능

HostListener - style

constructor (private elRef: ElementRef, private renderer: Renderer2) { }



@HostListener('mouseenter') mouseover(eventData: Event) {

this.renderer.setStyle(this.elRef.nativeElement, 'background-color', 'blue', false, false);

}



@HostListener('mouseleave') mouseleave(eventData: Event) {

this.renderer.setStyle(this.elRef.nativeElement, 'background-color', 'transparent', false, false);

}

HostListener, HostBinding - style

@HostBinding('style.backgroundColor') backgroundColor: string = 'transparent';

constructor (private elRef: ElementRef, private renderer: Renderer2) { }



@HostListener('mouseenter') mouseover(eventData: Event) {

  this.backgroundColor = 'blue';

}



@HostListener('mouseleave') mouseleave(eventData: Event) {

  this.backgroundColor = 'transparent';

}

TS file

@Directive({
  selector: '[appHighlight]'
)}

@Input() defaultColor: string = 'transparent';
@Input() highlightColor: string = 'blue';
@HostBinding('style.backgroundColor') backgroundColor: string = 'transparent';

constructor (private elRef: ElementRef, private renderer: Renderer2) { }

ngOnInit() {
  this.backgroundColor = this.defaultColor;
}

@HostListener('mouseenter') mouseover(eventData: Event) {
  this.renderer.setStyle(this.elRef.nativeElement, 'background-color', 'blue', false, false);
  this.backgroundColor = this.highlightColor;
}

@HostListener('mouseleave') mouseleave(eventData: Event) {
  this.renderer.setStyle(this.elRef.nativeElement, 'background-color', 'transparent', false, false);
  this.backgroundColor = this.defaultColor;
}

HTML file

<p appHighlight [defaultColor]="yellow" [highlightColor]="red"> Style me with a better directive!</p>

 

유용한 드롭다운 Directive - class

class에 open을 삽입함으로써 작동하게 하는 bootstrap 코드입니다.


Closing the Dropdown From Anywhere

If you want that a dropdown can also be closed by a click anywhere outside (which also means that a click on one dropdown closes any other one, btw.), replace the code of dropdown.directive.ts by this one (placing the listener not on the dropdown, but on the document):

TS file

import {Directive, ElementRef, HostBinding, HostListener} from '@angular/core';
 
@Directive({
	selector: '[appDropdown]'
})

export class DropdownDirective {
@HostBinding('class.open') isOpen = false;
@HostListener('document:click', ['$event']) toggleOpen(event: Event) {
	this.isOpen = this.elRef.nativeElement.contains(event.target) ? !this.isOpen : false;
}
constructor(private elRef: ElementRef) {}

출처(102번 강좌) https://www.udemy.com/share/1000imAkEeeVhSQ34=/


반응형

'프로그래밍 > Angular' 카테고리의 다른 글

Routes  (0) 2019.06.17
Providers - Hierarchical Injector  (0) 2019.06.16
style 스타일링  (0) 2018.09.05
FormsModule With ViewChild  (0) 2018.09.05
Component- Event  (0) 2018.08.12
반응형

다이나믹하게 표현하는 방법 입니다.


클래스를 이용한 방법. (css, html)


[ngClass]를 이용하여 스타일을 적용 시킬 수 있습니다.


CSS

.even {
color: #0000FF;
}


HTML

<li
class="list-group-item"
[ngClass]="{even: number %2 == 0}"
*ngFor="let number of Numbers">
{{number}}
</li>


{className: operation}

연산된 값은 false | ture 입니다.


스타일을 이용한 방법. (html)


[ngStyle]을 사용한 방법


HTML

<li
class="list-group-item"
[ngClass]="{even: number %2 == 0}"
[ngStyle]="{backgroundColor: number %2 === 0 ? 'yellow' : 'transparent'}"
*ngFor="let number of Numbers">
{{number}}
</li>


{propertyName: operation}

연산된 값은 #FFFF00와 같이 입력되어야 합니다.

 .even-bg {

background-color: #FFFF00;
}


Attribute Directive 를 이용한 방법. (ts, html)


ts파일을 생성하여 간편하게 사용할 수 있습니다.


HTML 태그의 속성 처럼 사용이 가능합니다.


ts

import { Directive, ElementRef, OnInit } from '@angular/core';
@Directive({
selector: '[appBasicHighlight]'
})
export class BasicHighlightDirective implements OnInit {
constructor(private elementRef: ElementRef) {
}

ngOnInit() {
this.elementRef.nativeElement.style.backgroundColor = 'green';
}
}


HTML

<p appBasicHighlight>Style me with basic directive!</p>


하지만 이 방법은 native 엘레먼트에 직접적으로 상호작용(interact)할 수 없으므로 renderer2를 사용을 권장합니다.


import { Directive, Renderer2, OnInit, ElementRef } from '@angular/core';

@Directive({
selector: '[appBetterHighlight]'
})
export class BetterHighlightDirective implements OnInit {

constructor(private elRef: ElementRef, private renderer: Renderer2) { }

ngOnInit() {
this.renderer.setStyle(this.elRef.nativeElement, 'background-color', 'blue')
}
}


반응형

'프로그래밍 > Angular' 카테고리의 다른 글

Providers - Hierarchical Injector  (0) 2019.06.16
custom attribute Directive  (0) 2019.06.09
FormsModule With ViewChild  (0) 2018.09.05
Component- Event  (0) 2018.08.12
Input decoretor  (0) 2018.08.12
반응형

HTML에서 Input에 있는 값을 [(ngModel)]이 아닌 ViewChild데코레이터를 사용하는 방법입니다.


<HTML>

<label for="name">Name</label>
<input
type="text"
id="name"
class="form-control"
#nameInput>


<TS>

@ViewChild('nameInput') nameInputRef: ElementRef;

getInputName() {
return this.nameInputRef.nativeElement.value;
}


레퍼런스를 이용하여 해당 엘레먼트의 값을 가져오는 방식입니다.


HTML의 input 태그의 #nameInput이 @ViewChild('nameInput')을 함으로써 바인딩 됩니다.


nameInputRef의 타입은 엥귤러/코어 에 있는 ElementRef를 이용하여 접근합니다.

ElementRef를 이용함으로써 native값에 접근할 수 있습니다.

반응형

'프로그래밍 > Angular' 카테고리의 다른 글

custom attribute Directive  (0) 2019.06.09
style 스타일링  (0) 2018.09.05
Component- Event  (0) 2018.08.12
Input decoretor  (0) 2018.08.12
Component- DataBinding  (0) 2018.08.11
반응형

@Output - angular/core (컴포넌트 이벤트 바인딩)


-EventEmitter를 이용하여, emit을 실행함. 주의, 제네릭에 클래스를 넣어 진행.


Tip: @Output('customName') 을 사용하면, 외부로 노출되는 @Output() 변수명 : {}의 변수명을 노출하지 않음.


작동 원리 :

0. ts의 EventEmitter 및 onComponentEevet가 선언.

1. component.hmtl의 click이벤트 발생.

2. eventDirective에 EventEmitter가 주입되어 있으므로, eventDirective.emit을 통하여 event를 날림.

3. parent.html의 onParentEvent()로 연결된 이벤트를 실행함.




parent.component.ts

eventElements = [{type: 'customEvent', name: 'Testevent', content: 'Just a test!'}];

onEventAdded(eventData: {eventName: string, eventContent: string}) {
this.eventElements.push({
type: 'customEvent',
name: eventData.eventName,
content: eventData.eventContent
});
}


parent.component.html

<div class="container">
<app-cockpit (eventCreated)="onEventAdded($event)">
</app-cockpit>
</div>


cockpit.component.ts

@Output() eventCreated = new EventEmitter<{eventName: string, eventContent: string}>();
newEventName = '';
newEventContent = '';

onAddEvent() {
this.eventCreated.emit({
eventName: this.newEventName,
eventContent: this.newEventContent
});
}


cockpit.component.html

<div class="row">
<div class="col-xs-12">
<label>Event Name</label>
<input type="text" class="form-control" [(ngModel)]="newEventName">
<label>Event Content</label>
<input type="text" class="form-control" [(ngModel)]="newEventContent">
<br>
<button
class="btn btn-primary"
(click)="onAddEvent()">Add Event</button>
</div>
</div>



반응형

'프로그래밍 > Angular' 카테고리의 다른 글

style 스타일링  (0) 2018.09.05
FormsModule With ViewChild  (0) 2018.09.05
Input decoretor  (0) 2018.08.12
Component- DataBinding  (0) 2018.08.11
Pipe  (0) 2018.06.07
반응형

옵션

설명
bindingPropertyName

데코레이터는 클레스의 필드에 입력 속성으로 마킹하는 기능을 하며 메타데이터 구성을 제공합니다. 바인딩된 데이터의 입력 속성을 명시하면 앵귤러는 자동적으로 값을 업데이트 합니다.

옵션

데코레이터는 클레스의 필드에 입력 속성으로 마킹하는 기능을 하며 메타데이터 구성을 제공합니다. 바인딩된 데이터의 입력 속성을 명시하면 앵귤러는 자동적으로 값을 업데이트 합니다.

bindingPropertyName: string

컴포넌트가 인스턴스화 할 때, 템플릿에서 마음껏 다른 이름을 사용할 수 있도록 바인드 속성의 이름을 맵핑합니다.

기본적으로, 바인드된 속성의 원래 이름이 입력 바인딩을 위해서 사용됩니다.

아래 예제에서 2개의 입력 속성을 갖는 컴포넌트를 만들었습니다, 1개는 특별한 바인딩 이름을 갖습니다.

  1. @Component({
  2. selector: 'bank-account',
  3. template: `
  4. Bank Name: {{bankName}}
  5. Account Id: {{id}}
  6. `
  7. })
  8. class BankAccount {
  9. // 이 속성은 바운드된 본래의 이름을 사용합니다.
  10. @Input() bankName: string;
  11. // 템플릿에서 이 컴포넌트가 인스턴스화 될 때
  12. // 이 속성 값은 다른 속성 이름으로 바운드 됩니다.
  13. @Input('account-id') id: string;
  14.  
  15. // 이 속성은 바운드되지 않으며, 앵귤러가 자동적으로 업데이트할 수 없습니다.
  16. normalizedBankName: string;
  17. }
  18.  
  19. @Component({
  20. selector: 'app',
  21. template: `
  22. <bank-account bankName="RBC" account-id="4747"></bank-account>
  23. `
  24. })
  25.  
  26. class App {}


반응형

'프로그래밍 > Angular' 카테고리의 다른 글

FormsModule With ViewChild  (0) 2018.09.05
Component- Event  (0) 2018.08.12
Component- DataBinding  (0) 2018.08.11
Pipe  (0) 2018.06.07
NgModules  (0) 2018.06.07
반응형

@Input() - angular/core (컴포넌트 데이터 바인딩)


public으로 외부 컴포넌트에서 접근할 수 있도록 도와주는 데코레이터


parent component.ts

componentElements = [{type: 'example', name: 'Testexample', content: 'Just a test!'}];


parent component.html

<app-component-element
*ngFor="let componentElement of componentElements"
[customPropertyName]="componentElement"></app-component-element>

html에서는 []를 이용하여 속성으로 접근. 


component.ts
import { Input } from '@angular/core';

@Input('customPropertyName') element : {type: string, name: string, content: string};

(component.ts의 데코레이터 변수명(element)에 따라 데코레이터의 패러미터를 달리 할 수 있음.

속성명과 @Input 데코레이터의 변수명이 다를경우: @Input('customPropertyName') element: {}

속성명과 @Input 데코레이터의 변수명이 같은경우: @Input() element: {} 


component.html

<div class="panel panel-default">
<div class="panel-heading">{{ element.name }}</div>
<div class="panel-body">
<p>
<strong *ngIf="element.type === 'example'" style="color: red">{{ element.content }}</strong>
</p>
</div>
</div>



응용한 예제 보기


API 보기 (블로그, 원본)

반응형

'프로그래밍 > Angular' 카테고리의 다른 글

FormsModule With ViewChild  (0) 2018.09.05
Component- Event  (0) 2018.08.12
Input decoretor  (0) 2018.08.12
Pipe  (0) 2018.06.07
NgModules  (0) 2018.06.07
반응형

모든 어플리케이션은 간단한 작업을로 시작합니다: 데이터를 받고, 변형시키고, 사용자에게 보여주는 기능입니다.

얻은 데이터는 생성하기 간편한 지역변수 이거나 스트리밍하는 복잡한 데이터로 웹 소캣 위 에서 가능합니다.


data가 도착하면, 그 값을 곧바로 view에 직접 toString 값으로  넣을 수 있는데, 드물지만 이러한 작업은 가끔 좋은 UX를 제공하곤합니다.

예를 들자면, 대부분의 사용 방법으로, 

사용자가 날짜를 April 16, 1988과 같은 간단한 양식을 

Fri Apr 15 1988 00:00:00 GMT-0700 (Pacific Daylight Time) 처럼 보길 좋아하지 않습니다.


많은 어플리케이션을 반복적으로 같은 변경을 적용할 경우에 사용할 수 있습니다.
이러한 작업을 스타일처럼  사용할 수 있습니다. 실제로, HTML 템플릿으로 style처럼 사용할 수 있습니다.


앵귤러 pipe를 소개하자면, display-value를 변경시켜 쓰는 방식으로 HTML에 선언하여 사용할 수 있습니다.


live example / download example 으로 실행해 볼 수 있으며, Stackblitz나 download를 할 수 있습니다.


사용 하기

pipe는 데이터를 input 으로 받아 원하는 형태로 변환 시켜 표현할 수 있습니다. 
이 페이지에서, 컴포넌트의 birthday 속성을 통해 사람이 보기 편한 날짜 형식으로 변경하는 pipe를 사용하게 됩니다.

src/app/hero-birthday1.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-hero-birthday',
  template: `<p>The hero's birthday is {{ birthday | date }}</p>`
})
export class HeroBirthdayComponent {
  birthday = new Date(1988, 3, 15); // April 15, 1988
}

컴포넌트의 템플릿에 주목해 보겠습니다.


src/app/app.component.html
<p>The hero's birthday is {{ birthday | date }}</p>

{{ }} 표현 안쪽을 보면, 컴포넌트의 birthday 값이 pipe 연산자 ( | )를 통해 오른쪽에 있는 Date pipe 함수로 흘러갑니다.

모든 pipe는 이러한 방식으로 작동합니다.


제공하는(built-in) pipe


앵귤러는  DatePipeUpperCasePipeLowerCasePipeCurrencyPipe, 그리고 PercentPipe 와 같은 파이프를 갖고 있습니다.

모두 어떤 템플릿에서든지 사용 가능합니다.



다른 pipe를 좀 더 알고 싶다면 API 레퍼런스의 pipes topics를 참고하세요. "pipe" 단어를 포함하는 것을 필터링 해보세요.

앵귤러는 FilterPipe OrderByPipe를 갖고 있지 않는 이유를 앵귤러 페이지 Appendix에 설명해 놓았습니다.



반응형

'프로그래밍 > Angular' 카테고리의 다른 글

FormsModule With ViewChild  (0) 2018.09.05
Component- Event  (0) 2018.08.12
Input decoretor  (0) 2018.08.12
Component- DataBinding  (0) 2018.08.11
NgModules  (0) 2018.06.07
반응형

NgModules


기본적으로 아래 두 개념을 알아두셔야 합니다.


NgModules 은 주입기(injector)와 컴파일러로 구성며, 관련있는 것들을 조직화 하는것을 돕습니다.


NgModule은 @NgModule 데코레이터로 마킹된 하나의 클래스 입니다. 

@NgModule은 런타임에 주입기를 어떻게 생성하고, 컴포넌트의 템플릿을 어떻게 컴파일 할지 기술하는  메타데이터 객체를 갖습니다. 

이것은 모듈이 소유한 컴포넌트, Directives(HTML의 속성으로 확장된 ng-app), pipes( {{value | pipe_function}} ), exports 속성을 통해서 일부를 public으로 만들어 , 외부 컴포넌트가 사용할 수 있도록 합니다.

물론 @NgModule를 사용하여 어플리케이션 의존성 주입기에 service provider로 추가할 수 있습니다.


예제로 pages cover에 관련된 NgModuels의 모든 기술들을 보여줍니다. live example / download example 를 참고하세요.

각 기술에 대한 설명은, 아래 있는 NgModules 섹션을 눌러 참고하세요.


Angular modularity (앵귤러 모듈화 링크)

모듈은 외부 라이브러리로부터의 확장과 어플리케이션 조직화하기에 훌륭한 방법입니다.


앵귤러 라이브러리는 NgModule이고,  FormsModuleHttpClientModule, 그리고 RouterModule 를 예로 들 수 있습니다.

많은 third-party 라리브러리는 NgModule로써 사용가능합니다 예를 들어  Material DesignIonic, 와 AngularFire2가 있습니다.


NgModule은 컴포넌트, directive, 그리고 재사용 가능한 기능 단위의 pipe, 비지니스 도메인 어플리케이션, *workflow 또는 유틸의 common collection을 하나로 병합해 놓았습니다.


*workflow
workflow는 extensibleService를 확장합니다. 

말인 즉슨 모든 workflow는 extensibleService로부터 제공하는 method와 속성을 상속합니다.

확장은 자기만의 step을 추가하거나, 존재하는 step을 제거하거나, custom 데이터를 조작하는 로직을 주입할 수 있도록 허용합니다.


또한 모듈은 어플리케이션에 service를 추가할 수 있습니다. 

내부적으로 개발자가 스스로 개발 한 것이나 외부의 소스로써 Angular router 나 HTTP client같은것도 추가할 수 있습니다.


모듈은 어플리케이션이 시작될 때  router(라우터)에 의해 eagerly로 로딩되거나 비동기(asynchronously)한 lazy 로딩을 합니다. 


NgModule 메타데이터는 다음과 같습니다 :


  • 모듈에 포함된 컴포넌트, directiv, pipe를 선언합니다.
  • 다른 모듈의 컴포넌트 템플릿이 사용할 수 있도록 컴포넌트, directive, pipe를 public으로 만듭니다.
  • 컴포넌트, directive, pipe를 갖는 다른 모듈을 임포트합니다.
  • 다른 어플리케이션 컴포넌트가 사용할 수 있도록 service를 제공합니다.
모든 앵귤러 앱은 적어도 1개의 모듈을 갖고 있으며, 루트 모듈입니다. 어플리케이션은 부트스트랩 모듈을 작동시킵니다.
루트 모듈은 몇개의 컴포넌트로 구성된 간단한 어플리케이션 입니다. 앱이 진화할수록, 루트 모듈안에 있는 feature modules 을 리팩토링 하면 됩니다.
그 다음 루트 모듈에 해당 모듈들을 임포트하면 됩니다.

NgModule의 기본 (The basic NgModule)

CLI는 앱을 새로 만들 때 다음과 같은 기본 앱 모듈을 생성합니다.

src/app/app.module.ts
// imports
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';

import { AppComponent } from './app.component';
import { ItemDirective } from './item.directive';


// @NgModule decorator with its metadata
@NgModule({
  declarations: [
    AppComponent,
    ItemDirective
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

가장 위에 있는 임포트 문에서. 그 다음 섹션은 어디에 @Ngmodule가 어떤 컴포넌트와 directive가 속해져있는지 명세하고 이것(선언)으로 다른 모듈이 사용하기 쉽도록 합니다(imports). 이 페이지는  Bootstrapping은 NgModule의 구성을 포함하여 빌딩되었습니다. 만약 @NgModule의 구성에 대해 더 알고 싶다면,  Bootstrapping을 읽어보세요.

반응형

'프로그래밍 > Angular' 카테고리의 다른 글

FormsModule With ViewChild  (0) 2018.09.05
Component- Event  (0) 2018.08.12
Input decoretor  (0) 2018.08.12
Component- DataBinding  (0) 2018.08.11
Pipe  (0) 2018.06.07
반응형

아이오닉은 src/index.html로 앱을 시작합니다.


<!-- 아이오닉의 루트 컴포넌트로 app을 불러온다. -->  
<ion-app></ion-app>

<!-- 빌딩 과정에서 polyfills js를 생성한다. -->
<!-- 브라우저 API를 보충하는 역할이다. -->
<!-- polyfills는 브라우저에서 지원하지 않는 API일 지라도 개발자가 사용할 수 있도록 도와준다. -->
<script src="build/polyfills.js"></script>

<!-- 빌딩 과정에서 vendoer.js를 생성한다. --> 
<!-- node_modules의 모든 의존성(dependencies)를 포함한다. -->
<script src="build/vendor.js"></script>
<!-- 빌딩 과정에서 bundle js를 생성한다. --> 
<!-- 모들 파일을 1개의 묶음 파일로 만드는 과정 -->
<script src="build/main.js"></script>

./src/


src 디렉토리에서 Ionic 앱을 구동하는 대부분의 코드를 작성합니다.

ionic serve

를 실행시키면, src/ 안에 있는 코드는 transpiled (언어를 다른 언어로 번역) 됩니다. 

아이오닉에서는 타입스크립트(TypeScript)를 작성하면, 브라우저가 이해할 수 있는  ES5로 transpile 합니다. 


src/app/app.module.ts 

앵귤러의 모듈의 구성과 해당 모듈의 링크를 나타냅니다.

@NgModule({
declarations: [MyApp, HomePage, ListPage],
imports: [BrowserModule, IonicModule.forRoot(MyApp)],
bootstrap: [IonicApp],
entryComponents: [MyApp, HomePage, ListPage],
providers: [StatusBar, SplashScreen, {provide: ErrorHandler, useClass: IonicErrorHandler}]
})
export class AppModule {}

@NgModule 데코레이터로 나타내고, 각 구성 항목을 연결하게 됩니다.

runtime에 메타데이터 객체(컴포넌트 템플릿을 컴파일하는 방식과 주입기 생성하는 방식을 작성하는 객체)를 사용합니다. 


*참고

아래 개념을 기본적으로 알고 있어야 합니다.


반응형

'프로그래밍 > Ionic' 카테고리의 다른 글

State 관리  (0) 2019.10.12
Styling & Theming  (0) 2019.10.12
유용한 컴포넌트 소개  (0) 2019.10.12
캐패시터로 네이티브 앱 만들기  (0) 2019.09.25
Ionic Component Basics  (0) 2019.09.18
반응형

캐시(난이도: 하) 알고리즘 문제



하편



 4. 도시 객체 배열을 처리할 수 있는 객체를 생성합니다.


입력 형식 1 : 캐시 크기(cacheSize)와 도시이름 배열(cities)을 입력받는다.
입력 형식 3 : cities는 도시 이름으로 이뤄진 문자열 배열로, 최대 도시 수는 100,000개이다.

조건 1 : 캐시 교체 알고리즘은 LRU(Least Recently Used)를 사용한다.


도시이름을 객체로 만든것처럼, 도시이름 배열을 일급 컬랙션 객체로 만들어 처리합니다.
배열로 한다면... 너무 번거로운 작업이 많아져 List로 처리하였습니다.

@Test
public void 도시_포함() {
Cities cities = new Cities(getStringArr("Jeju", "Pangyo"));
assertThat(true, is(cities.contains(new City("Jeju"))));
}

@Test
public void 도시_삭제() {
Cities cities = new Cities(getStringArr("Jeju"))
.addFirst(new City("aaa"))
.remove(new City("aaa"));

assertThat(false, is(cities.contains(new City("aaa"))));
}

@Test
public void 도시_앞쪽으로_추가() {
Cities cities = new Cities(getStringArr("Jeju"))
.addFirst(new City("aaa"));
cities.remove(cities.size()-1);

assertThat(true, is(cities.contains(new City("aaa"))));
}
package kakao.cache;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Cities {
private static final int MAX_LENGTH = 100001;
private List<City> cities;

public Cities() {
cities = new ArrayList<>();
}

public Cities(String... cities) {
if (cities == null) throw new IllegalArgumentException();
make(Arrays.asList(cities).stream()
.map(City::new)
.collect(Collectors.toList()));
}

public Cities(List<City> cities) {
if (cities == null) throw new IllegalArgumentException();
make(cities);
}

private void make(List<City> cities) {
if (cities.size() > MAX_LENGTH) throw new IllegalArgumentException();
this.cities = new ArrayList<>();
this.cities.addAll(cities);
}

public boolean contains(City city) {
return cities.stream().anyMatch(_city -> _city.equals(city));
}

public Cities addFirst(City city) {
cities.add(0, city);
return new Cities(cities);
}

public Cities remove(City city) {
cities.remove(city);
return new Cities(cities);
}

public Cities remove(int index) {
cities.remove(index);
return new Cities(cities);
}

public int size() {
return cities.size();
}

public Stream<City> stream() {
return cities.stream();
}
}


addFirst() : 캐시 교체 알고리즘은 LRU(Least Recently Used)를 충족하기 위해 사용했습니다.

가장 최근에 사용된 것이 front(0번 인덱스)에 위치하고, 최근이 아닐 수록 rear로 index가 밀리게 됩니다.


remove() : 체이닝이 되도록 다시 객체를 반환합니다.
[테스트 하기 편하기 위해서 체이닝 했을뿐, 반드시 객체를 반환할 필요는 없습니다.]


객체에 사용된 count를 넣는 방식도 있지만 불필요한 인스턴스 필드를 생성하여 관리 이슈가 생기는 것을 피하기 위해 index를 기준으로 하였습니다.


5. 캐시 크기 객체 만들기


입력 형식 1 : 캐시 크기(cacheSize)와 도시이름 배열(cities)을 입력받는다.
입력 형식 2 : cacheSize는 정수이며, 범위 0 ≦ cacheSize ≦ 30 이다.


다른 값이 들어올 수 없도록 객체로 만들면 관리 이슈가 줄어들게 됩니다.

@Test
public void cacheSize_0_일때() {
new CacheSize(0);
}
package kakao.cache;

public class CacheSize {
private static final int MAX_SIZE = 31;

private Integer size;

public CacheSize(Integer size) {
if (size < 0 || size > MAX_SIZE) throw new IllegalArgumentException();
this.size = size;
}

public int toInt() {
return size;
}

public boolean same(int number) {
return size == number;
}
}

해설에 보면 공개된 대부분의 LRU 구현 코드는 0일 때 비정상적인 작동을 한다는군요.


생성자에 넣어서 생성된다면, 입력 형식 2의 범위를 커버할 수 있습니다.



6.캐시 객체 생성


지금까지 만든 것들을 조합하면, 캐시 객체가 완성 됩니다.

@Test
public void LRU_cacheSize_0_작동() {
Cache first = new Cache(0)
.executeLRU(new City("first"));
assertThat(false, is(first.contains(new City("first"))));
}

@Test
public void LRU_cacheSize_작동() {
Cache hasSecond = new Cache(1)
.executeLRU(new City("first"))
.executeLRU(new City("second"));

assertThat(false, is(hasSecond.contains(new City("first"))));
assertThat(true, is(hasSecond.contains(new City("second"))));
}
package kakao.cache;

public class Cache {

private CacheSize cacheSize;
private Cities cities;

public Cache(CacheSize cacheSize, Cities cities) {
this.cacheSize = cacheSize;
this.cities = cities;
}

public Cache(Integer cacheSize) {
this.cacheSize = new CacheSize(cacheSize);
cities = new Cities();
}

public Cache executeLRU(City city) {
if (cacheSize.same(0)) return new Cache(cacheSize.toInt());

if (contains(city)) {
remove(city);
}

if (isFull()) {
remove(lastIndex());
}

return addFirst(city);
}

public boolean contains(City city) {
return cities.contains(city);
}

private boolean isFull() {
return cities.size() >= cacheSize.toInt();
}

private int lastIndex() {
return cities.size()-1;
}

private Cache addFirst(City city) {
return new Cache(cacheSize, cities.addFirst(city));
}

private Cache remove(City city) {
return new Cache(cacheSize, cities.remove(city));
}

private Cache remove(int index) {
return new Cache(cacheSize, cities.remove(index));
}
}


LRU는 가장 최근에 사용된 것이 캐시에 남아 있어야 함으로, 사이즈가 1일 경우 가장 최근에 사용한 것만 갖고 있도록 하였습니다.

executeLRU()
1. 캐시 사이즈가 0인 예외적인 상황이 아니라면 객체를 front에 추가 합니다.
2. 해당 값을 갖고 있다면, 추가 전 삭제합니다.
3. 캐시에 없는 값이 들어왔다면, rear(오랫동안 사용하지 않은 객체)를 삭제합니다.

lastIndex() : 가독성을 위해, 한 줄 짜리 매쏘드를 궃이 사용했습니다.
(-1과 같은 숫자가 들어오는걸 피하고 싶었습니다.)

 캐시는 완성이 되었습니다.



7. 실행 객체 생성

OOP인 만큼 실행 또한 객체로 대체합니다.

@Test
public void 결과_확인() {
assertThat(50, is(new CityCache(3)
.executeTime(
getStringArr("Jeju", "Pangyo", "Seoul", "NewYork", "LA", "Jeju", "Pangyo", "Seoul", "NewYork", "LA")
)));

assertThat(21, is(new CityCache(3)
.executeTime(
getStringArr("Jeju", "Pangyo", "Seoul", "Jeju", "Pangyo", "Seoul", "Jeju", "Pangyo", "Seoul")
)));

assertThat(60, is(new CityCache(2)
.executeTime(
getStringArr("Jeju", "Pangyo", "Seoul", "NewYork", "LA", "SanFrancisco", "Seoul", "Rome","Paris", "Jeju", "NewYork", "Rome")
)));

assertThat(52, is(new CityCache(5)
.executeTime(
getStringArr("Jeju", "Pangyo", "Seoul", "NewYork", "LA", "SanFrancisco", "Seoul", "Rome","Paris", "Jeju", "NewYork", "Rome")
)));

assertThat(16, is(new CityCache(2)
.executeTime(
getStringArr("Jeju", "Pangyo", "NewYork", "newyork")
)));

assertThat(25, is(new CityCache(0)
.executeTime(
getStringArr("Jeju", "Pangyo", "Seoul", "NewYork", "LA")
)));
}
package kakao.cache;

public class CityCache {

private Cache cache;
private ExecuteResult executeResult;

public CityCache(Integer cacheSize) {
cache = new Cache(cacheSize);
executeResult = new ExecuteResult();
}

public int executeTime(String... cities) {
new Cities(cities).stream().forEachOrdered(this::execute);
return executeResult.sum();
}

private void execute(City city) {
executeResult.increase(cache.contains(city));
cache.executeLRU(city);
}
}

executeTime() : forEachOrdered로 진행되어야만 LRU가 되겠죠?


execute() : this::excute로 표현하기위해 매쏘드로 뺐습니다.


주저리 주저리...
네이밍이 너무나 어렵네요...

Cache에 LRU알고리즘이 있어야하는데, 정작 사용하는 콜렉션은 Cities를 사용하고 있으니.. 

CitiCache가 적합할 것 같고... 여러가지 고민을 하던중 외부에서 사용할 때, Cache보단 CityCache가 하위처럼 보이기 때문에 CityCache로 하였습니다.


원래는 Cache가 Abstract class로 있고, CityCache와 CityCacheVO로 구현하면 확장하기 좋지만 일급 콜렉션에 대한 공부가 부족하다는 것을 느끼며 구현에 실패하게 되었습니다.


LRU도 객체로 빼야하나 고민도 하다가 알고리즘인 만큼 매쏘드로 하였습니다.


반응형

+ Recent posts