서브 클래스 유형을 요구하지 않는 좋은 설계 방법은 무엇입니까?


11

프로그램이 객체의 클래스를 알아야 할 때 일반적으로 디자인 결함을 나타내므로 이것을 처리하는 좋은 방법이 무엇인지 알고 싶습니다. Circle, Polygon 또는 Rectangle과 같이 상속 된 다른 하위 클래스로 Shape 클래스를 구현하고 있으며 Circle이 Polygon 또는 Rectangle과 충돌하는지 알 수있는 다른 알고리즘이 있습니다. 그런 다음 Shape의 두 인스턴스가 있고 하나가 다른 인스턴스와 충돌하는지 알고 싶다고 가정합니다.이 메소드에서 호출 해야하는 알고리즘을 알기 위해 충돌하는 객체가 어떤 서브 클래스 유형인지 유추해야합니다. 나쁜 디자인이나 연습? 이것이 내가 해결 한 방법입니다.

abstract class Shape {
  ShapeType getType();
  bool collide(Shape other);
}

class Circle : Shape {
  getType() { return Type.Circle; }

  bool collide(Shape other) {
    if(other.getType() == Type.Rect) {
      collideCircleRect(this, (Rect) other);     
    } else if(other.getType() == Type.Polygon) {
      collideCirclePolygon(this, (Polygon) other);
    }
  }
}

이것은 나쁜 디자인 패턴입니까? 하위 클래스 유형을 유추하지 않고 어떻게 이것을 해결할 수 있습니까?


1
원과 같은 모든 인스턴스는 다른 모든 셰이프 유형을 알고 있습니다. 그래서 그들은 모두 어떻게 든 견고하게 연결되어 있습니다. 그리고 Triangle과 같은 새로운 모양을 추가하자마자 어디서나 삼각형 지원을 추가하게됩니다. 그것은 당신이 더 자주 바꾸고 싶은 것에 달려 있으며, 새로운 모양을 추가 할 것입니다.이 디자인은 나쁩니다. 솔루션 스프롤이 있으므로 삼각형에 대한 지원이 모든 곳에 추가되어야합니다. 대신 충돌 감지를 별도의 클래스로 추출해야합니다.이 클래스는 모든 유형 및 위임에서 작동 할 수 있습니다.
thepacker

가능한 복제 조건부 논리
gnat

IMO는 성능 요구 사항에 따라 다릅니다. 보다 구체적인 코드는 코드가 최적화되고 실행 속도가 빨라집니다. 맞춤 충돌 검사 될 수 있기 때문에, 특히이 경우 (이를 구현도)에서, 유형 선택하면 OK이다 enourmously 빠른 일반적인 솔루션보다. 그러나 런타임 성능이 중요하지 않은 경우 항상 일반적인 / 다형성 접근 방식을 사용합니다.
marstato

덕분에 필자의 경우 성능이 중요하고 새로운 모양을 추가하지 않을 것입니다. CollisionDetection 접근 방식을 사용하고 있지만 "Type getType ()"메서드를 CollisionDetection 클래스에서 Shape를 사용하여 "인스턴스"를 수행합니까?
Alejandro

1
추상 Shape객체 사이에는 효과적인 충돌 절차가 없습니다 . 경계점에 대한 충돌을 확인하지 않는 한 논리는 다른 객체의 내부에 의존합니다 bool collide(x, y)( 제어점 의 하위 집합이 절충 될 수 있음). 그렇지 않으면 어떻게 든 유형을 확인해야합니다. 추상화가 실제로 필요한 경우 Collision유형 을 생성하는 경우 (현재 액터 영역 내의 객체에 대한) 올바른 접근 방식이어야합니다.
shudder

답변:


13

다형성

사용 getType()하거나 이와 비슷한 것을 사용하는 한 다형성을 사용하지 않는 것입니다.

어떤 타입인지 알아야한다는 느낌이 듭니다. 그러나 당신이 알고 싶어하면서하고 싶은 일은 실제로 수업에 푸시되어야한다는 것입니다. 그런 다음 언제해야하는지 알려 주면됩니다.

절차 코드는 정보를 얻은 다음 결정을 내립니다. 객체 지향 코드는 객체에게 작업을 수행하도록 지시합니다.
— 알렉 샤프

이 원칙은 tell, do n't ask라고 합니다. 이를 수행하면 유형과 같은 세부 사항을 전파하지 않고 해당 세부 사항에 작용하는 논리를 작성할 수 없습니다. 그렇게하면 수업이 시작됩니다. 클래스 내에서 해당 동작을 유지하는 것이 좋습니다. 따라서 클래스가 변경 될 때 변경 될 수 있습니다.

캡슐화

당신은 나에게 다른 모양이 필요하지 않다고 말할 수 있지만 나는 당신을 믿지 않으며 당신도 믿지 않아야합니다.

다음 캡슐화의 좋은 효과는 세부 사항들이에 표시 코드에 퍼져하지 않기 때문에이 새로운 유형을 쉽게 추가 할 수 있다는 것입니다 ifswitch논리. 새로운 유형의 코드는 모두 한 곳에 있어야합니다.

무식 충돌 감지 시스템

유형에 신경 쓰지 않고 성능이 우수하고 2D 모양으로 작동하는 충돌 감지 시스템을 설계하는 방법을 보여 드리겠습니다.

여기에 이미지 설명을 입력하십시오

당신이 그걸 그려야한다고 가정 해보십시오. 간단 해 보입니다. 모든 서클입니다. 충돌을 이해하는 원 클래스를 만들고 싶어합니다. 문제는 1000 개의 원이 필요할 때 엇갈리는 사고 방식을 제시한다는 것입니다.

우리는 서클에 대해 생각해서는 안됩니다. 우리는 픽셀에 대해 생각해야합니다.

이 사람을 그리는 데 사용하는 것과 동일한 코드가 사용자가 언제 만지거나 클릭하는 사람을 감지하는 데 사용할 수있는 코드라고 말하면 어떻게됩니까?

여기에 이미지 설명을 입력하십시오

여기에 각 원을 고유 한 색상으로 그렸습니다 (눈이 검은 색 외곽선을보기에 충분할 경우 무시하십시오). 즉,이 숨겨진 이미지의 모든 픽셀이 다시 그려진 것을 의미합니다. 해시 맵이 잘 처리합니다. 실제로 이런 방식으로 다형성을 할 수 있습니다.

이 이미지는 사용자에게 보여줄 필요가 없습니다. 첫 번째 코드와 동일한 코드로 작성합니다. 다른 색상으로.

사용자가 원을 클릭하면 하나의 원만 해당 색상이기 때문에 정확히 어느 원인지 알 수 있습니다.

다른 위에 원을 그리면 세트에 덤프하여 덮어 쓰려는 모든 픽셀을 빠르게 읽을 수 있습니다. 설정 포인트가 완료되면 충돌 한 모든 원을 가리키고 충돌을 알리기 위해 각 원을 한 번만 호출하면됩니다.

새로운 유형 : 사각형

이것은 모두 원으로 이루어졌지만 나는 당신에게 묻습니다 : 직사각형과 다르게 작동합니까?

감지 시스템에 원 지식이 유출되지 않았습니다. 반경, 원주 또는 중심점은 신경 쓰지 않습니다. 픽셀과 색상이 중요합니다.

이 충돌 시스템에서 개별 모양으로 밀려 야하는 유일한 부분은 고유 한 색상입니다. 그 외에는 도형이 도형을 그리는 것에 대해서만 생각할 수 있습니다. 어쨌든 그들이 잘하는 것입니다.

이제 충돌 논리를 작성할 때 어떤 하위 유형이 있는지 상관하지 않습니다. 충돌하도록 지시하고 그리는 척하는 모양에서 찾은 것을 알려줍니다. 유형을 알 필요가 없습니다. 즉, 다른 클래스에서 코드를 업데이트하지 않고도 원하는 수의 하위 유형을 추가 할 수 있습니다.

구현 선택

실제로 고유 한 색상 일 필요는 없습니다. 실제 객체 참조 일 수 있으며 간접적 인 수준을 저장합니다. 그러나이 답변에 그려지면 그다지 좋지 않을 것입니다.

이것은 하나의 구현 예입니다. 분명히 다른 사람들이 있습니다. 이것이 보여 주려는 것은 이러한 모양 하위 유형이 단일 책임을 갖도록할수록 전체 시스템이 더 잘 작동한다는 것입니다. 더 빠르고 적은 메모리 집약적 솔루션이있을 수 있지만, 만약 그들이 서브 타입에 대한 지식을 널리 퍼 뜨리게된다면 성능 향상에도 불구하고 그것들을 사용하는 것을 싫어할 것입니다. 분명히 필요하지 않으면 사용하지 않을 것입니다.

이중 파견

지금까지 double dispatch을 완전히 무시했습니다 . 할 수 있었기 때문에 그렇게했습니다. 충돌 논리가 충돌 한 두 가지 유형을 신경 쓰지 않는 한 필요하지 않습니다. 필요하지 않으면 사용하지 마십시오. 필요하다고 생각되면 최대한 오래 다루십시오. 이 자세를 YAGNI 라고 합니다 .

실제로 다른 종류의 충돌이 필요하다고 판단되면 n 모양 하위 유형에 실제로 n 2 종류의 충돌 이 필요한지 스스로에게 물어보십시오 . 지금까지 다른 모양 하위 유형을 쉽게 추가 할 수 있도록 열심히 노력했습니다. 원에 사각형이 있음을 강제로 알리는 이중 디스패치 구현 으로 망치고 싶지 않습니다.

어쨌든 얼마나 많은 충돌이 있습니까? 약간의 추측 (위험한 것)은 탄성 충돌 (탄력), 비 탄력적 (끈적임), 활력 적 (폭발적) 및 파괴적 (모략 적)을 발명합니다. 더 많을 수도 있지만 이것이 n 2 보다 작 으면 충돌을 과도하게 설계하지 않아도됩니다.

이것은 내 어뢰가 손상을 입는 무언가를 때릴 때 우주선에 부딪쳤다는 것을 알 필요가 없다는 것을 의미합니다. "하하! 5 점의 피해를 입었습니다."

피해를 입은 물건은 피해 메시지를받는 물건에 피해 메시지를 보냅니다. 이렇게하면 다른 도형에 새 도형에 대해 알리지 않고 새로운 도형을 추가 할 수 있습니다. 새로운 유형의 충돌에만 확산됩니다.

우주선은 "하하! 100 포인트 데미지를 입었다." "당신은 이제 내 선체에 붙어 있습니다". 그리고 torp는 "음, 나에 대해 잊어 버렸습니다."

어느 쪽도 각각이 무엇인지 정확히 알지 못합니다. 그들은 충돌 인터페이스를 통해 서로 대화하는 방법을 알고 있습니다.

이제 더블 디스패치 (double dispatch)를 사용하면 이것보다 더 친밀하게 제어 할 수 있지만 실제로 원 하십니까?

그렇다면 적어도 실제 모양 구현이 아닌 모양이 받아들이는 충돌의 추상화를 통해 이중 디스패치를 ​​수행하는 것에 대해 생각하십시오. 또한 충돌 동작은 종속성으로 주입하여 해당 종속성에 위임 할 수있는 것입니다.

공연

성능은 항상 중요합니다. 그러나 이것이 항상 문제가되는 것은 아닙니다. 성능을 테스트하십시오. 추측하지 마십시오. 성능의 이름으로 다른 모든 것을 희생한다고해서 일반적으로 성능이 좋은 코드는 아닙니다.



+1 "다른 모양이 필요하지 않다고 말할 수는 있지만 믿지 않아요."
Tulains Córdova

이 프로그램이 도형 그리기가 아니라 순수한 수학 계산에 관한 것이라면 픽셀에 대해 생각하면 어디서나 얻을 수 없습니다. 이 답변은 인식 된 객체 지향 순도로 모든 것을 희생해야 함을 의미합니다. 또한 모순이 포함되어 있습니다. 먼저 미래에 더 많은 유형의 도형이 필요할 수 있다는 아이디어를 바탕으로 전체 디자인을 작성해야한다고 말한 다음 "YAGNI"라고 말합니다. 마지막으로, 유형을 쉽게 추가 할 수 있다는 것은 종종 작업을 추가하기가 어렵다는 것을 의미합니다. 이는 유형 계층 구조가 비교적 안정적이지만 작업이 많이 바뀌면 나빠집니다.
Christian Hackl

7

당신과 같은 문제 소리의 설명을 사용해야합니다 Multimethods 이 특별한 경우 (일명 여러 파견) - 더블 파견 . 첫 번째 대답은 래스터 렌더링에서 충돌하는 모양을 일반적으로 처리하는 방법에 대한 길이가 길었지만 OP가 "벡터"솔루션을 원했거나 OOP 설명의 고전적인 예 인 Shapes로 전체 문제가 재구성 된 것으로 생각합니다.

인용 된 Wikipedia 기사에서도 동일한 충돌 은유를 사용하므로 인용하겠습니다. (Python에는 다른 언어와 같은 내장 다중 방법이 없습니다) :

@multimethod(Asteroid, Asteroid)
def collide(a, b):
    """Behavior when asteroid hits asteroid"""
    # ...define new behavior...
@multimethod(Asteroid, Spaceship)
def collide(a, b):
    """Behavior when asteroid hits spaceship"""
    # ...define new behavior...
# ... define other multimethod rules ...

다음 질문은 프로그래밍 언어로 멀티 메소드를 지원하는 방법입니다.



대답에 추가 Multimethods 일명 여러 파견의 예, 특별한 경우
로마 수시

5

이 문제는 두 가지 수준에서 재 설계가 필요합니다.

먼저 셰이프에서 셰이프 간 충돌을 감지하기위한 논리를 추출해야합니다. 따라서 모델에 새 모양을 추가해야 할 때마다 OCP를 위반하지 않습니다 . Circle, Square 및 Rectangle이 이미 정의되어 있다고 가정하십시오. 그런 다음 다음과 같이 할 수 있습니다.

class ShapeCollisionDetector
{
    public void DetectCollisionCircleCircle(Circle firstCircle, Circle secondCircle)
    { 
        //Code that detects collision between two circles
    }

    public void DetectCollisionCircleSquare(Circle circle, Square square)
    {
        //Code that detects collision between circle and square
    }

    public void DetectCollisionCircleRectangle(Circle circle, Rectangle rectangle)
    {
        //Code that detects collision between circle and rectangle
    }

    public void DetectCollisionSquareSquare(Square firstSquare, Square secondSquare)
    {
        //Code that detects collision between two squares
    }

    public void DetectCollisionSquareRectangle(Square square, Rectangle rectangle)
    {
        //Code that detects collision between square and rectangle
    }

    public void DetectCollisionRectangleRectangle(Rectangle firstRectangle, Rectangle secondRectangle)
    { 
        //Code that detects collision between two rectangles
    }
}

다음으로, 호출하는 모양에 따라 적절한 메소드가 호출되도록 정렬해야합니다. 다형성과 방문자 패턴 을 사용하여이를 수행 할 수 있습니다 . 이를 위해서는 적절한 객체 모델이 있어야합니다. 먼저 모든 모양이 동일한 인터페이스를 준수해야합니다.

    interface IShape
{
    void DetectCollision(IShape shape);
    void Accept (ShapeVisitor visitor);
}

다음으로, 부모 방문객 클래스가 있어야합니다 :

    abstract class ShapeVisitor
{
    protected ShapeCollisionDetector collisionDetector = new ShapeCollisionDetector();

    abstract public void VisitCircle (Circle circle);

    abstract public void VisitSquare(Square square);

    abstract public void VisitRectangle(Rectangle rectangle);

}

각 방문자 객체에 ShapeCollisionDetector유형 속성이 있어야하기 때문에 인터페이스 대신 클래스를 사용하고 있습니다 .

모든 IShape인터페이스 구현은 적절한 방문자를 인스턴스화하고 다음과 Accept같이 호출 오브젝트가 상호 작용하는 오브젝트 의 적절한 메소드를 호출합니다.

    class Circle : IShape
{
    public void DetectCollision(IShape shape)
    {
        CircleVisitor visitor = new CircleVisitor(this);
        shape.Accept(visitor);
    }

    public void Accept(ShapeVisitor visitor)
    {
        visitor.VisitCircle(this);
    }
}

    class Rectangle : IShape
{
    public void DetectCollision(IShape shape)
    {
        RectangleVisitor visitor = new RectangleVisitor(this);
        shape.Accept(visitor);
    }

    public void Accept(ShapeVisitor visitor)
    {
        visitor.VisitRectangle(this);
    }
}

특정 방문자는 다음과 같습니다.

    class CircleVisitor : ShapeVisitor
{
    private Circle Circle { get; set; }

    public CircleVisitor(Circle circle)
    {
        this.Circle = circle;
    }

    public override void VisitCircle(Circle circle)
    {
        collisionDetector.DetectCollisionCircleCircle(Circle, circle);
    }

    public override void VisitSquare(Square square)
    {
        collisionDetector.DetectCollisionCircleSquare(Circle, square);
    }

    public override void VisitRectangle(Rectangle rectangle)
    {
        collisionDetector.DetectCollisionCircleRectangle(Circle, rectangle);
    }
}

    class RectangleVisitor : ShapeVisitor
{
    private Rectangle Rectangle { get; set; }

    public RectangleVisitor(Rectangle rectangle)
    {
        this.Rectangle = rectangle;
    }

    public override void VisitCircle(Circle circle)
    {
        collisionDetector.DetectCollisionCircleRectangle(circle, Rectangle);
    }

    public override void VisitSquare(Square square)
    {
        collisionDetector.DetectCollisionSquareRectangle(square, Rectangle);
    }

    public override void VisitRectangle(Rectangle rectangle)
    {
        collisionDetector.DetectCollisionRectangleRectangle(Rectangle, rectangle);
    }
}

이런 식으로 새 모양을 추가 할 때마다 모양 클래스를 변경할 필요가 없으며 적절한 충돌 감지 방법을 호출하기 위해 모양 유형을 확인할 필요가 없습니다.

이 솔루션의 단점은 새 셰이프를 추가 할 경우 해당 셰이프에 대한 메소드 (예 :)를 사용하여 ShapeVisitor 클래스를 확장해야하므로 VisitTriangle(Triangle triangle)다른 모든 방문자에게 해당 메소드를 구현해야한다는 것입니다. 그러나 이것은 확장이므로 기존 방법이 변경되지 않고 새로운 방법 만 추가된다는 점에서 OCP를 위반하지 않으며 코드 오버 헤드가 최소화됩니다. 또한 class를 사용하면 SRPShapeCollisionDetector 위반 을 피하고 코드 중복을 피할 수 있습니다.


5

기본적인 문제는 대부분의 최신 OO 프로그래밍 언어에서 함수 오버로드가 동적 바인딩과 함께 작동하지 않는다는 것입니다 (즉, 함수 인수의 유형은 컴파일 타임에 결정됨). 필요한 것은 하나가 아닌 두 개의 객체에서 가상 인 가상 메소드 호출입니다. 이러한 방법을 다중 방법이라고 합니다 . 그러나 Java, C ++ 등과 같은 언어에서는 이 동작에뮬레이트 하는 방법 이 있습니다 . 이중 디스패치 가 매우 유용한 곳입니다.

기본 아이디어는 다형성을 두 번 사용한다는 것입니다. 두 도형이 충돌하면 다형성을 통해 객체 중 하나의 올바른 충돌 방법을 호출하고 일반 도형 유형의 다른 객체를 전달할 수 있습니다. 호출 된 메소드 에서이 객체가 원, 사각형 또는 무엇 인지 알 수 있습니다. 그런 다음 전달 된 모양 객체에서 콜리 전 메서드를 호출 하고이 객체에 전달 합니다. 그런 다음이 두 번째 호출은 다형성을 통해 올바른 객체 유형을 다시 찾습니다.

abstract class Shape {
  bool collide(Shape other);
  bool collide(Rect other);
  bool collide(Circle other);
}

class Circle : Shape {

  bool collide(Shape other) {
    return other.collide(this);
  }

  bool collide(Rect other) {
    // algorithm to detect collision between Circle and Rect
  }

  // ...
}

class Rect : Shape {

  bool collide(Shape other) {
    return other.collide(this);
  }

  bool collide(Circle other) {
    // algorithm to detect collision between Circle and Rect
  }

  // ...
}

그러나이 기술의 큰 단점은 계층의 각 클래스가 모든 형제에 대해 알아야한다는 것입니다. 나중에 새 모양을 추가하면 유지 보수 부담이 커집니다.


2

어쩌면 이것이이 문제에 접근하는 가장 좋은 방법은 아닙니다.

수학 베잉 모양 충돌은 모양 조합에 따라 다릅니다. 필요한 서브 루틴의 수가 시스템이 지원하는 모양의 제곱임을 의미합니다. 셰이프 충돌은 실제로 셰이프에 대한 작업이 아니라 셰이프를 매개 변수로 사용하는 작업입니다.

운영자 과부하 전략

기본 수학 문제를 단순화 할 수 없다면 연산자 과부하 접근 방식을 권장합니다. 다음과 같은 것 :

 public final class ShapeOp 
 {
     static { ... }

     public static boolean collision( Shape s1, Shape s2 )  { ... }
     public static boolean collision( Point p1, Point p2 ) { ... }
     public static boolean collision( Point p1, Square s1 ) { ... }
     public static boolean collision( Point p1, Circle c1 ) { ... }
     public static boolean collision( Point p1, Line l1 ) { ... }
     public static boolean collision( Square s1, Point p2 ) { ... }
     public static boolean collision( Square s1, Square s2 ) { ... }
     public static boolean collision( Square s1, Circle c1 ) { ... }
     public static boolean collision( Square s1, Line l1 ) { ... }
     (...)

정적 초기화 기에서는 리플렉션을 사용하여 일반 충돌 (Shape s1, Shape s2) 메소드에서 다이너 믹 디스 패서를 구현하는 메소드 맵을 작성합니다. 정적 초기화 기는 또한 누락 된 충돌 함수를 감지하고이를보고하는 클래스를로드하는 것을 거부하는 논리를 가질 수 있습니다.

이것은 C ++ 연산자 오버로드와 비슷합니다. C ++에서는 오버로드 할 수있는 고정 된 심볼 세트가 있기 때문에 연산자 오버로드가 매우 혼동됩니다. 그러나이 개념은 매우 흥미롭고 정적 함수로 복제 할 수 있습니다.

이 접근 방식을 사용하는 이유는 충돌이 객체에 대한 작업이 아니기 때문입니다. 충돌은 임의의 두 객체에 대한 관계를 나타내는 외부 작업입니다. 또한 정적 초기화 프로그램에서 충돌 기능이 누락되었는지 확인할 수 있습니다.

가능하면 수학 문제를 단순화하십시오

앞에서 언급했듯이 충돌 함수 수는 모양 유형 수의 제곱입니다. 이것은 단지 20 개의 모양을 가진 시스템에서 400 개의 루틴이 필요하고 21 개의 모양은 441 등이 필요함을 의미합니다. 이것은 쉽게 확장 할 수 없습니다.

그러나 수학을 단순화 할 수 있습니다 . 충돌 기능을 확장하는 대신 모든 모양을 래스터 화하거나 삼각 측량 할 수 있습니다. 그렇게하면 충돌 엔진을 확장 할 필요가 없습니다. 충돌, 거리, 교차, 병합 및 기타 여러 기능이 보편적입니다.

삼각 분할

대부분의 3D 패키지 및 게임이 모든 것을 삼각 측량하는 것을 보셨습니까? 그것은 수학을 단순화하는 형태 중 하나입니다. 이것은 2D 모양에도 적용됩니다. 폴리를 삼각 측량 할 수 있습니다. 원과 스플라인은 폴리곤에 근사 할 수 있습니다.

다시 ... 하나의 충돌 기능이 있습니다. 그러면 수업이됩니다 :

public class Shape 
{
    public Triangle[] triangulate();
}

그리고 당신의 작업 :

public final class ShapeOp
{
    public static boolean collision( Triangle[] shape1, Triangle[] shape2 )
}

더 간단하지 않습니까?

래스터 화

단일 충돌 기능을 갖도록 모양을 래스터화할 수 있습니다.

래스터 화는 근본적인 해결책 인 것처럼 보이지만 모양 충돌이 얼마나 정밀해야하는지에 따라 저렴하고 빠를 수 있습니다. 그것들이 (게임 에서처럼) 정확할 필요가 없다면, 저해상도 비트 맵이있을 수 있습니다. 대부분의 응용 프로그램은 수학에서 절대 정밀도가 필요하지 않습니다.

근사치가 충분할 수 있습니다. 생물학 시뮬레이션을위한 ANTON 슈퍼 컴퓨터가 그 예입니다. 이 수학은 계산하기 어려운 많은 양자 효과를 버리고 지금까지의 시뮬레이션은 실제 실험과 일치합니다. 게임 엔진 및 렌더링 패키지에 사용되는 PBR 컴퓨터 그래픽 모델은 단순화하여 각 프레임을 렌더링하는 데 필요한 컴퓨터 전력을 줄입니다. 실제로는 정확하게 정확하지는 않지만 육안으로 설득력을 갖기에 충분합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.