실행 주변 패턴이 있는 상용구 코드를 제거해 보겠습니다.



이 기사에서는 먼저 전통적인 접근 방식을 사용하여 처음부터 스레드로부터 안전한 스택을 생성한 다음 주변 실행 설계 패턴으로 리팩터링할 것입니다.

스택은 last-in-first-out (LIFO) 데이터 구조입니다. pushpop 두 가지 주요 방법이 있습니다. 만들어 봅시다 -

package com.bazlur;

import java.util.Arrays;
import java.util.NoSuchElementException;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ConcurrentStack<T> {
  private final Lock lock = new ReentrantLock();
  private T[] elements;
  private int size = -1;

  public ConcurrentStack() {
    this(10);
  }

  public ConcurrentStack(int initialCapacity) {
    this.elements = (T[]) new Object[initialCapacity];
  }

  public void push(T value) {
    lock.lock();
    try {
      growIfNeeded();
      elements[size] = value;
    } finally {
      lock.unlock();
    }
  }

  public T pop() {
    lock.lock();
    try {
      if (size == -1) {
        throw new NoSuchElementException();
      }
      trimToSizeIfNeeded();

      var element = elements[size];
      elements[size] = null;
      size--;
      return element;
    } finally {
      lock.unlock();
    }
  }

  private void growIfNeeded() {
    if (++size == elements.length) {
      grow();
    }
  }

  private void grow() {
    int newCapacity = elements.length * 2;
    elements = Arrays.copyOf(elements, newCapacity);
  }

  private void trimToSizeIfNeeded() {
    if (size < elements.length) {
      elements = Arrays.copyOf(elements, size + 1);
    }
  }
}



이것은 가장 간단한 구현이며 많은 단점이 있습니다. 어쨌든 그것은 요점을 벗어났습니다.

푸시 앤 팝 방식을 살펴보십시오. 한 가지는 매우 일반적입니다. 자물쇠를 잠근 다음 잠금을 해제합니다. 이것은 상용구처럼 보이며 스택의 실제 비즈니스 로직과는 아무 관련이 없습니다.

이 상용구 코드를 다른 곳으로 옮기자-

package com.bazlur;

import java.util.concurrent.locks.Lock;
import java.util.function.Supplier;

public class LockHelper {
  public static void withLock(Lock lock, Runnable runnable) {
    lock.lock();
    try {
      runnable.run();
    } finally {
      lock.unlock();
    }
  }

  public static <T> T withLock(Lock lock, Supplier<T> supplier) {
    lock.lock();
    try {
      return supplier.get();
    } finally {
      lock.unlock();
    }
  }
}



첫 번째 방법은 잠금과 실행 가능 항목을 사용합니다. 잠금 및 조회를 수행하고 그 사이에 runnable의 run 메소드를 실행합니다. 다음 메서드도 잠금과 공급자를 사용합니다. 무언가를 반품해야 하는 경우에 대비하여 여기에서 공급업체를 사용했습니다.

ConcurrentStack에서 이러한 메서드를 사용해 봅시다.

package com.bazlur;

import java.util.Arrays;
import java.util.NoSuchElementException;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ConcurrentStack2<T> {
  private final Lock lock = new ReentrantLock();
  private T[] elements;
  private int size = -1;

  public ConcurrentStack2() {
    this(10);
  }

  public ConcurrentStack2(int initialCapacity) {
    this.elements = (T[]) new Object[initialCapacity];
  }

  public void push(T value) {
    LockHelper.withLock(lock, () -> {
      growIfNeeded();
      elements[size] = value;
    });
  }

  public T pop() {
    return LockHelper.withLock(lock, () -> {
      if (size == -1) {
        throw new NoSuchElementException();
      }
      trimToSizeIfNeeded();

      var element = elements[size];
      elements[size] = null;
      size--;
      return element;
    });
  }

  private void growIfNeeded() {
    if (++size == elements.length) {
      grow();
    }
  }

  private void grow() {
    int newCapacity = elements.length * 2;
    elements = Arrays.copyOf(elements, newCapacity);
  }

  private void trimToSizeIfNeeded() {
    if (size < elements.length) {
      elements = Arrays.copyOf(elements, size + 1);
    }
  }
}



이제 우리의 코드는 훨씬 깨끗해졌고 관련 코드를 잠그는 상용구가 없습니다.

이 클래스에 다른 메서드를 추가하려면 비슷한 패턴을 사용합니다. –

public T peek() {
    return LockHelper.withLock(lock, () -> elements[size]);
}



우리는 눈앞에 존재하지 않는 것처럼 보이는 무언가에 대해 코드를 실행하고 노이즈를 제거하고 명확성을 가져옵니다. 이것이 바로 디자인 패턴 주변 실행이라고 하는 이유이며 유사한 상용구 코드를 많이 제거하는 데 도움이 됩니다.

오늘은!

건배!!

복사/붙여넣기: https://github.com/rokon12/100DaysOfJava/blob/main/src/main/java/com/bazlur/ConcurrentStack2.java

좋은 웹페이지 즐겨찾기