Skeleton Screen ?

최근 웹페이지를 들어가면 로딩중에 저런 화면을 많이 본적이 있을 것이다.

유투브, 페이스북에서도 로딩중에 많이 사용 하는 화면인데 일명 "Skeleton Screen" 이라고 부른다.

 

CSS linear-gradient를 통한 Skeleton Screen

linear-gradient() CSS 함수는 두 개 이상의 색이 직선을 따라 점진적으로 변화하는 이미지를 생성한다. 함수의 결과는 <image>의 특별한 종류인 <gradient>자료형이다.

<gradient> <image>의 한 종류로서 <image>를 사용하는 곳에만 적용할 수 있다. 따라서 linear-gradient()를 background-color  <color> 자료형을 받는 속성에는 사용할 수 없다.

ex1) 사용법 linear-gradient([driection], #color1, #color2) (2개 이상의 색상은필수이다.) (driection 에는 top, left, right deg 등 다양한 인자를 사용 가능하다.)

.example-element {
  width:300;
  height:400;
  background: linear-gradient(0, blue, black);
}

 

정 중앙을 기준으로 두개의 색이 그라데이션됨

 

- direction 부분에 degree를 줘서 각도만큼 비틀수도 있다. ingredient는 축을기준으로 무수한 선을 긋는 개념이기때문에, 각도를 준다면 다음처럼 축을 기준으로 휘어진 선이 휘게된다.

 

ex2) 축을 45도만큼 휘게 사용

.example-element {
  width:300;
  height:400;
  background: linear-gradient(45deg, blue, black);
}

정사각형이 아니므로 정확한 대각선은 아닐수도..

 

CSS linear-gradient 을 활용하여 게시글을 표현해보자.

ex3) linear-gradient 를 이용해서 글 한줄 한줄을 표현해보자.

.example-element {
  width:300;
  height:400;

  background-image:
    linear-gradient(lightgrey 15px, transparent 0),
    linear-gradient(lightgrey 15px, transparent 0),
    linear-gradient(lightgrey 15px, transparent 0);
}

 

- 오잉? 근데 3분명히 3줄을 추가했는데 한줄만 나온다? 왜그럴까??? -> 현재 3줄의 좌표가 겹쳐있기때문이다.

background-position: [x 위치, y 위치] 를 추가해서 각각 ingredient의 좌표를 달리해보자.

 

.example-element {
  width:300;
  height:400;

  background-image:
    linear-gradient(lightgrey 15px, transparent 0),
    linear-gradient(lightgrey 15px, transparent 0),
    linear-gradient(lightgrey 15px, transparent 0);
  
  background-position:
  /* 좌측에서 5px만큼 y축으로 5px(선굵기-간격[15px-20px])씩 띄움 */
    5px 10px,
    5px 30px,
    5px 50px;
  background-size:
  /* gradient 기준으로 width height 만한 사각형을 만든다고 생각 하자*/
    100px 100px,
    150px 100px, 
    150px 100px;  
  background-repeat: repeat-y;
}

- 또한 나는 background-size 라는 속성을 주었는데, 사용법은 width 값과 height 값을주면된다. 이 값은 gradient 자체의 width와 height가 아닌 gradient 부터 시작하는 사각형을 준다고 생각하면 된다. 이를 명시하는 이유는 background-repeat: repeat-y; 라는 속성을 주면 y축으로 gradient를 계속 반복할 수있다.

 

width heigth 값은 gradient 선의 속성이 아님을 유의하자

 

 

- 이제 제법 그럴싸하지않은가?? 뭔가좀 허전하니 마지막으로, 애니메이션만 추가해보자.

See the Pen example by EdgarHan (@EdgarHan) on CodePen.

 

 

 

 

 

 

출처

https://developer.mozilla.org/ko/docs/Web/CSS/linear-gradient

https://wit.nts-corp.com/2018/11/19/5371

 

고전적인 싱글턴 패턴

- 객체가 하나만 필요한 경우는 굉장히 많다, 스레드풀, 레지스트리 설정 객체 아니면 그래픽카드객체 등등... 이런경우에는 객체가 한가지만 필요하다.

- 분명히 static 전역 객체를 쓰면, 구지 싱글턴을 안써도 될텐데....? 라는 말을 할 수 있을것이다. 그렇지만 메모리 효율이라는 명목아래 우리는 싱글턴 패턴을 쓴다.

public class Singleton {
	private static Singleton unique;
    
    //접근 제한자를 써서 외부에서 객체를 생성못하게함.
    private Singleton() {}
    
    public static Singleton getInstance () {
    	if (unique == null) {
        	uniqueInstance = new Singleton();
        }
        return unique;
    }

}

 

- 위와같은 코드는 누구나 한번쯤 봤을것이다. 그러나, 이런 식으로 싱글턴을 쓰지않도록 주의해야 한다.

 

문제 제기

- 우리는 초콜릿바 공장을 운영하고 있고, 초콜릿을 녹이는 가마솥이 있다고 가정하자, 이 솥은 컴퓨터로 제어된다.

- 이 가마솥에서는 초콜릿과 우유를 받아서 끓이고 다음공정으로 넘기는 역할을 한다. 빈 가마솥을 끓이다거나 가득찬 가마솥에 끓인다거나 하는 실수는 절대 하지말아야한다.

public class ChocolateGamasot {
	private boolean empty;
	private boolean heat;
	
	public ChocolateGamasot() {
		empty = true;
		heat = false;
	}
	
	//가마솟이 비어있을때만 집어넣음.
	public void fill() {
		if(isEmpty()) {
			empty = false;
			heat = false;
			// 원유를 집어넣음
		}
	}
	
	public void drain() {
		if(!isEmpty() && isHeat()) {
			//끓인 재료를 다음 단계로 넘김
			empty = true;
		}
	}
	
	public void boil() {
		if(isEmpty() && isHeat()) {
			// 끓임
			heat = true;
		}
	}
	
	private boolean isHeat() {
		return heat;
	}

	public boolean isEmpty() {
		return empty;
	}
}

 

- 이런 가마솥 객체를 만들었다고 가정하자, 가마솥 객체가 두개가 있으면 어떻게될까?

- 대개가 있으면 안되므로 이를 싱글턴으로 바꿔보자.

public class ChocolateGamasot {
	
	private static ChocolateGamasot unique;
	private boolean empty;
	private boolean heat;
	
	private ChocolateGamasot() {
		empty = true;
		heat = false;
	}
	
	public static ChocolateGamasot getInstance() {
		if(unique == null) {
			unique = new ChocolateGamasot();
		}
		return unique;
	}
	
	//가마솟이 비어있을때만 집어넣음.
	public void fill() {
		if(isEmpty()) {
			empty = false;
			heat = false;
			// 원유를 집어넣음
		}
	}
	
	public void drain() {
		if(!isEmpty() && isHeat()) {
			//끓인 재료를 다음 단계로 넘김
			empty = true;
		}
	}
	
	public void boil() {
		if(isEmpty() && isHeat()) {
			// 끓임
			heat = true;
		}
	}
	
	private boolean isHeat() {
		return heat;
	}

	public boolean isEmpty() {
		return empty;
	}
}

 

- 위와같이 싱글턴으로 바꾼뒤 이 소스를 실행시켰더니 문제가생겼다.

//Main 함수
ChocolateGamasot gamasot = ChocolateGamasot.getInstance();

gamasot.fill();
gamasot.boil();
gamasot.drain();

- JVM 내에 두개의 스래드가 위의 코드로 접근했는데 2개의 객체가 생긴것이다!!! (이럴수가!!!)
1번스레드가 if === null 을 확인하고, 그 다음구문을 실행하던중, 다음 스레드가 똑같이 이프문 안으로 분기된것이다.

 

	public static synchronized ChocolateGamasot getInstance() {
		if(unique == null) {
			unique = new ChocolateGamasot();
		}
		return unique;
	}

- 위와같이 동기화 하여 문제를 해결하긴 했는데, 왠지 오버헤드가 너무아까운 생각이든다.. 일단 unique 변수에 ChocolateGamasot를 한번 초기화하기만하면 구지 getInstance() 전체 함수에 동기화를 거는 오버헤드는 필요 없을듯하다.

 

더 효율적으로 getInstance의 동기성을 제어하자

1. getInstance() 의 속도가 중요하지않다면 그냥 둔다.
메소드를 동기화하면 성능이 100배정도 저하된다고한다. 그럼에도 불구하고 getInstance의 속도가 그닥 중요하지않다면 놔두도록하자.

2. 인스턴스를 필요할때 생성하지않고, 바로 생성하여둔다.

	private static ChocolateGamasot unique = new ChocolateGamasot();
	private boolean empty;
	private boolean heat;
	
	public static ChocolateGamasot getInstance() {
		return unique;
	}

- 위 같은방법도 그닥 나빠보이지 않는다 (근데 static 변수만 쓸때와 차이점은 뭐지??)

3."DCL(Dobule Checking Locking)"을 써서 getInstance()에서 동기화 되는 부분을 줄여보자.

private static volatile ChocolateGamasot unique;
private boolean empty;
private boolean heat;

public static ChocolateGamasot getInstance() {
	
	//인스턴스가 있는지 확인하고 synchronized 블럭으로 접근
	if(unique == null) {
		// 이렇게하면 처음에만 동기화 진행.
		synchronized (ChocolateGamasot.class) {
			if(unique == null) {
				unique = new ChocolateGamasot();
			}
		}
	}
	return unique;
}

 

- 잠깐! volatile 이란 무엇일까?
“… the volatile modifier guarantees that any thread that reads a field will see the most recently written value.” - Josh Bloch

이를 해석해보자면, volatile 이란 어떤 쓰레드가 read 하더라도 항상 최근값만 불러온다는 뜻이다.

Volatile을 사용하게 된다면, RAM에서 직접 변수를 읽고 쓰도록 Thread에게 강제한다. 이렇게 되면 스레드는 캐시에 존재하는 복사본이 아닌, RAM에 있는 마지막버전을 확인한다. 스레드가 동기화된 블록으로 들어가게 되면 ( 코드상 sychronizied 부분) 다른 모든 스레드는 첫번째 스레드가 동기화된 블록에서 나갈때 까지 기다린다. (캐시에 접근하지않고 직접 RAM에 직접 읽고 기록)

출처:https://stackoverflow.com/questions/106591/what-is-the-volatile-keyword-useful-for

 

What is the volatile keyword useful for

At work today, I came across the volatile keyword in Java. Not being very familiar with it, I found this explanation: Java theory and practice: Managing volatility Given the detail in which that

stackoverflow.com

c언어의 예시로 쉽게 volatile 설명 :https://dojang.io/mod/page/view.php?id=749

 

- 참고로 DCL은 자바 1.4 버전 이후부터만 쓸 수 있다. (1.4 이전 JVM 중에는 volatille 을 사용하더라도 동기화가 안되는 것이 많았다)

문제 제기

- 지난시간(링크). 우리는 피자가게 스토리와 엮어서 Factory Method Pattern에 대해 알아보았다.

- KorPizzaStore, IndiaPizzaStore를 만들어 Pizza 객체를 생성하는 createPizza에 대한 메소드는 Localize 된 피자스토어에 맡겼다. (India에서는 피자를 손으로 찢어먹기때문에 cutting 하지 않는다는 상황가정때문)

- IndiaPizzaStore에서는 cheese를 파니르(채식) 치즈와 채식 도우와 닭고기 불고기 토핑을 쓴다고 한다.

- 이런 차이를 극복하기위해 Abstaract Factorty Pattern을 써서 KorPizzaStore와 다르게 재료생산해보자.

 

- 위와같이 피자마다 같은구성요소를 가지긴 하지만, 지역마다 다른 방식으로 구성요소를 구현한다. 또한 위의 3재료뿐만아니라 피자마다 추가로 새로운 토핑 및  향신료도 추가될 수 있을것이다.

 

Abstract Factory Pattern을 스토리에 적용시켜보자.

public interface PizzaIngredientFactory {
	public Dough createDough();
	public Cheese createCheese();
	public Boolgogi createBoolgogi();
	//이건 지역별로 함께 쓸 수 있다.
	public Veggies[] createVeggies();
}

- 먼저 추상화된 재료 Pizza 재료 공장을 만들고, 지역별로 공장을 만들어보자.

- 도우, 불고기, 치즈는 다르지만, 베지는 공통적으로 함께 쓸 수있다.

 

public class IndiaPizzaIngredientFactory implements PizzaIngredientFactory{

	@Override
	public Dough createDough() {
		return new VeggieDough();
	}

	@Override
	public Cheese createCheese() {
		return new VeggieCheese();
	}

	@Override
	public Boolgogi createBoolgogi() {
		return new ChickenBoolgogi();
	}

	@Override
	public Veggies[] createVeggies() {
		Veggies veggies[] = {new Garlic(), new Onion(), new Mushroom()};
		return veggies;
	}
}

 

public class KorIngredientPizzaFactory implements PizzaIngredientFactory{

	@Override
	public Dough createDough() {
		return new OriginalDough();
	}

	@Override
	public Cheese createCheese() {
		return new OriginalCheese();
	}

	@Override
	public Boolgogi createBoolgogi() {
		// TODO Auto-generated method stub
		return new BeefBoolgogi();
	}

	@Override
	public Veggies[] createVeggies() {
		Veggies veggies[] = {new Garlic(), new Onion(), new Mushroom()};
		return veggies;
	}

}

 

- 위와같은식으로 나라에따라 피자 ingredient Factory를 만들었다.

- 이제 재료를 생산할 준비가 끝났다. 피자에 따라 재료가 달라지니 한번 Pizza Class를 바꿔보도록 하자.

 

public abstract class Pizza {
	String name;
	Dough dough;
	Cheese cheese;
	Boolgogi boolgogi;
	Veggies veggies[];
	
	//원재료 생산공장을 달리하면서 추상화시켯다.
	public abstract void preare();

	public void bake() {
		System.out.println("굽는중.....");
	}
	
	public void cut() {
		System.out.println("자르는중.....");
	}
	
	public void box() {
		System.out.println("박싱중.....");
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
}

- prepare와 원재료들을 변수 이외에는 달라진 로직은 없다. 그러면 Boolgogipizza 클래스를 한번 만들어보자.

public class BoolGogiPizza extends Pizza {
	PizzaIngredientFactory pizzaIngredientFactory;
	
	public BoolGogiPizza(PizzaIngredientFactory pizzaIngredientFactory) {
		setName( " Korea Style Cutting Pizza");
		this.pizzaIngredientFactory = pizzaIngredientFactory;
	}

	@Override
	public void prepare() {
		System.out.println("재료준비중... " +name);
		dough = pizzaIngredientFactory.createDough();
		cheese = pizzaIngredientFactory.createCheese();
		boolgogi = pizzaIngredientFactory.createBoolgogi();
		
		System.out.println(dough.getClass().getSimpleName());
		System.out.println(cheese.getClass().getSimpleName());
		System.out.println(boolgogi.getClass().getSimpleName());
	}
}

- 전 포스팅에서는 KorKorBoolGoggiPizza, IndiaBoolGoggiPizza 등, 구체적으로 로컬별 피자객체를 만들었으나, 이제는 ingredient Factory만 달리 생성하면 되므로 이런식으로 BoolGogiPizza 객체를 만들었다.

-다만 이제 KorPizzaStore에서 피자객체를 생성할때 코드가 바뀌어야한다..

//바뀌기전
public class KorPizzaStore extends PizzaStore {

	@Override
	protected Pizza createPizza(String type) {
		Pizza pizza= null;
		
		if(type.equals("boolgogi")) {
	        pizza = new KorBoolGoggiPizza();
	    } 
		
		return pizza;
	}

}

 

//바뀐후
public class KorPizzaStore extends PizzaStore {

	@Override
	protected Pizza createPizza(String type) {
		Pizza pizza= null;
		PizzaIngredientFactory fac = new KorIngredientPizzaFactory();
		
		if(type.equals("boolgogi")) {
			pizza = new BoolGogiPizza(fac);
			pizza.setName("Korea Style Pizza From KOR ingredient Factory");
	    } 
		
		return pizza;
	}

}

 

- 바뀌기전에는 피자 자체를 store에서 생성해주었지만, 이제는 원재료를 로컬별 IngredientPizzaFactory에서 받아쓰기때문에, IngredientPizzaFactory 객체를 BoolGogiPizza 생성자에 넘겨주어 피자 재료를 달리생성하게된다.

- 전 포스팅한 메인함수를 그대로 실행시켜보니 내가 의도한대로 잘 나온다.

 

Abstract Factory Pattern 내용 정리

- 지역별 PizzaStore에 피자라는 Product가 종속되어 있지않고, Abstract된 Ingredient Pizza Factory에 따라 제품을 받아쓰기 때문에, 프로그램이 실행중에도, 같은 불고기피자라도 다른 재료를 적용 시킬 수 있다.

 

 

 

 

Abstarct Factory Pattern 정의

추상 팩토리 패턴
추상 팩토리 패턴에서는 인터페이스를 이용하여 서로 연관된, 또는 서로 의존하는 객체를 구상 클래스를 지정하지 않고도 생성 할 수있다.

- 추상 팩토리 패턴을 이용하면, 인터페이스를 통해 Product 들을 생성 할 수있다. 
(pizza = new BoolgogiPizza(korPizzaIngredientFactory); 처럼 말이다.)

- 따라서 클라이언트와 Product를 분리시킬수가 있다.

 

- 위와같은 클래스 다이어그램으로 Abstract Factorty Pattern을 표현할 수 있다.

 

- 실제 우리가 만든 피자스토어의 클래스다이어그램은 이렇게 만들 수 있다.

 

Abstract Factory Pattern Vs Factory Method Pattern

 

- Abstract Factory Pattern은 Factory Method Pattern에 비해 다소 복잡하다.

- 새로운 Product를 추가하려면 Factory Method Pattern는 Interface를 바꿔야 한다. (India boolgogi Pizza에서 Original Dough를 쓰고싶다면?? 코드자체가 바뀌어야함)

- Abstract Factory Pattern에서는 소스코드 자체를 추가하지않고, 구현체만 갈아끼우면 해결 할 수있다.

'To be Developer > DesignPatterns' 카테고리의 다른 글

6-1.Command Pattern (Simple)  (0) 2020.01.10
5.Singleton Pattern  (0) 2020.01.07
4-1.Factory Pattern (Factory Method Pattern)  (0) 2020.01.04
3.Decorator Pattern  (0) 2020.01.02
2.Observer Pattern  (0) 2020.01.01

+ Recent posts