แทนที่เงื่อนไขด้วยความหลากหลายในวิธีที่เหมาะสม?


10

พิจารณาสองคลาสDogและCatทั้งสองสอดคล้องกับAnimalโปรโตคอล (ในแง่ของภาษาการเขียนโปรแกรม Swift นั่นคืออินเทอร์เฟซใน Java / C #)

เรามีหน้าจอที่แสดงรายการสุนัขและแมวที่หลากหลาย มีInteractorคลาสที่จัดการตรรกะเบื้องหลัง

ตอนนี้เราต้องการที่จะนำเสนอการแจ้งเตือนการยืนยันให้กับผู้ใช้เมื่อเขาต้องการที่จะลบแมว อย่างไรก็ตามสุนัขจะต้องถูกลบทันทีโดยไม่มีการแจ้งเตือนใด ๆ วิธีที่มีเงื่อนไขจะมีลักษณะเช่นนี้:

func tryToDeleteModel(model: Animal) {
    if let model = model as? Cat {
        tellSceneToShowConfirmationAlert()
    } else if let model = model as? Dog {
        deleteModel(model: model)
    }
}

รหัสนี้จะถูก refactored ได้อย่างไร? เห็นได้ชัดว่ามันมีกลิ่น

คำตอบ:


9

คุณปล่อยให้ชนิดโปรโตคอลตัวเองตรวจสอบพฤติกรรม คุณต้องการที่จะปฏิบัติโปรโตคอลทั้งหมดเดียวกันตลอดทั้งโปรแกรมของคุณยกเว้นในชั้นเรียนการดำเนินการเอง การทำเช่นนี้เป็นการเคารพหลักการทดแทนของ Liskov ซึ่งกล่าวว่าคุณควรจะสามารถผ่านCatหรือDog(หรือโปรโตคอลอื่น ๆ ที่คุณอาจมีภายใต้Animalอาจเกิดขึ้นในที่สุด) และทำให้มันทำงานอย่างไม่ใส่ใจ

ดังนั้นสันนิษฐานว่าคุณต้องการเพิ่มisCriticalfunc เพื่อAnimalที่จะดำเนินการโดยทั้งสองและDog Catการติดตั้งอะไรก็ตามDogจะส่งคืนความผิดพลาดและการดำเนินการใด ๆCatจะกลับมาเป็นจริง

ณ จุดนี้คุณจะต้องทำเท่านั้น (ขออภัยหากไวยากรณ์ไม่ถูกต้องไม่ใช่ผู้ใช้ Swift):

func tryToDeleteModel(model: Animal) {
    if model.isCritical() {
        tellSceneToShowConfirmationAlert()
    } else {
        deleteModel(model: model)
    }
}

มีปัญหาเพียงเล็กน้อยเท่านั้นและนั่นคือDogและCatเป็นโปรโตคอลซึ่งหมายความว่าพวกเขาในตัวเองไม่ได้กำหนดสิ่งที่isCriticalส่งกลับปล่อยให้สิ่งนี้กับทุกชั้นเรียนการดำเนินการเพื่อตัดสินใจด้วยตนเอง หากคุณมีมากของการใช้งานก็อาจจะคุ้มค่าในขณะที่คุณสร้างระดับขยายของCatหรือDogที่แล้วอย่างถูกต้องดำเนินการและมีประสิทธิภาพการล้างการเรียนการดำเนินงานตามทั้งหมดจากความต้องการที่จะแทนที่isCriticalisCritical

หากนี่ไม่ได้ตอบคำถามของคุณโปรดเขียนความคิดเห็นและฉันจะขยายคำตอบของฉัน!


มันไม่ค่อยชัดเจนในคำแถลงของคำถาม แต่DogและCatถูกอธิบายว่าเป็นคลาสขณะที่Animalโปรโตคอลนั้นถูกใช้งานโดยแต่ละคลาสเหล่านั้น ดังนั้นจึงมีความไม่ตรงกันระหว่างคำถามและคำตอบของคุณ
แม็กเคเล็บ

ดังนั้นคุณแนะนำให้รุ่นตัดสินใจว่าจะแสดงป๊อปอัพการยืนยันหรือไม่? แต่ถ้าหากมีตรรกะที่เกี่ยวข้องเช่นป๊อปอัพแสดงเฉพาะเมื่อมีแมว 10 ตัวแสดง ตรรกะขึ้นอยู่กับInteractorสถานะในขณะนี้
Andrey Gordeev

ใช่ขอโทษเกี่ยวกับคำถามที่ไม่ชัดเจนฉันได้ทำการแก้ไขเล็กน้อย ควรมีความชัดเจนมากขึ้นในขณะนี้
Andrey Gordeev

1
พฤติกรรมประเภทนี้ไม่ควรเชื่อมโยงกับโมเดล ขึ้นอยู่กับบริบทและไม่ใช่เอนทิตีเอง ฉันคิดว่าแมวกับหมาน่าจะเป็น POJO มากกว่า ควรจัดการพฤติกรรมในสถานที่อื่นและสามารถเปลี่ยนแปลงได้ตามบริบท การมอบหมายพฤติกรรมหรือวิธีการที่พฤติกรรมจะต้องพึ่งพาในแมวหรือสุนัขจะนำไปสู่ความรับผิดชอบมากเกินไปในชั้นเรียนดังกล่าว
Grégory Elhaimer

@ GrégoryElhaimerโปรดทราบว่ามันไม่ได้กำหนดพฤติกรรม มันเป็นเพียงการระบุว่ามันเป็นระดับที่สำคัญหรือไม่ พฤติกรรมตลอดทั้งโปรแกรมที่จำเป็นต้องรู้ว่ามันเป็นระดับที่สำคัญสามารถประเมินและปฏิบัติตาม ถ้านี่เป็นคุณสมบัติที่แตกต่างกับวิธีการในกรณีที่ทั้งสองCatและได้รับการจัดการก็สามารถและควรจะเป็นสถานที่ให้บริการทั่วไปในDog Animalการทำอย่างอื่นกำลังขอให้ปวดหัวการบำรุงรักษาในภายหลัง
Neil

4

บอกกับถาม

วิธีตามเงื่อนไขที่คุณแสดงเราจะเรียกว่า " ถาม " นี่คือที่ลูกค้าที่บริโภคถามว่า "คุณเป็นคนประเภทไหน?" และกำหนดพฤติกรรมและการโต้ตอบกับวัตถุให้เหมาะสม

ตรงกันข้ามกับทางเลือกที่เราเรียกว่า " บอก " ด้วยการใช้tellคุณจะต้องผลักดันงานใน polymorphic implementations ให้มากขึ้นเพื่อให้การใช้งานไคลเอนต์ code นั้นง่ายขึ้นโดยไม่มีเงื่อนไขและพบได้บ่อย

เนื่องจากคุณต้องการใช้การแจ้งเตือนยืนยันคุณสามารถทำให้ความสามารถที่ชัดเจนของอินเทอร์เฟซ ดังนั้นคุณอาจมีวิธีบูลีนที่เลือกตรวจสอบกับผู้ใช้และส่งกลับบูลีนการยืนยัน return true;ในชั้นเรียนที่ไม่ต้องการที่จะยืนยันพวกเขาก็แทนที่ด้วย การใช้งานอื่น ๆ อาจกำหนดแบบไดนามิกหากพวกเขาต้องการใช้การยืนยัน

ไคลเอ็นต์บริโภคมักจะใช้วิธีการยืนยันโดยไม่คำนึงถึงประเภทรองโดยเฉพาะอย่างยิ่งก็คือการทำงานกับซึ่งจะทำให้การปฏิสัมพันธ์บอกแทนการถาม

(วิธีการอีกวิธีหนึ่งคือการยืนยันการลบ แต่จะทำให้ลูกค้าที่คาดหวังว่าการดำเนินการลบจะประสบความสำเร็จ)


ดังนั้นคุณแนะนำให้รุ่นตัดสินใจว่าจะแสดงป๊อปอัพการยืนยันหรือไม่? แต่ถ้าหากมีตรรกะที่เกี่ยวข้องเช่นป๊อปอัพแสดงเฉพาะเมื่อมีแมว 10 ตัวแสดง ตรรกะขึ้นอยู่กับInteractorสถานะในขณะนี้
Andrey Gordeev

2
ตกลงใช่ว่าเป็นคำถามอื่นที่ต้องการคำตอบที่แตกต่างกัน
Erik Eidt

2

การพิจารณาว่าจำเป็นต้องมีการยืนยันหรือไม่เป็นความรับผิดชอบของCatชั้นเรียนดังนั้นให้เปิดใช้งานเพื่อดำเนินการดังกล่าว ฉันไม่รู้ Kotlin ดังนั้นฉันจะแสดงสิ่งต่าง ๆ ใน C # หวังว่าไอเดียนี้จะสามารถถ่ายโอนไปยัง Kotlin ด้วย

interface Animal
{
    bool IsOkToDelete();
}

class Cat : Animal
{
    private readonly Func<bool> _confirmation;

    public Cat (Func<bool> confirmation) => _confirmation = confirmation;

    public bool IsOkToDelete() => _confirmation();
}

class Dog : Animal
{
    public bool IsOkToDelete() => true;
}

จากนั้นเมื่อสร้างCatอินสแตนซ์คุณTellSceneToShowConfirmationAlertจะต้องส่งคืนซึ่งจะต้องส่งคืนtrueหากตกลงเพื่อลบ:

var model = new Cat(TellSceneToShowConfirmationAlert);

จากนั้นฟังก์ชั่นของคุณจะกลายเป็น:

void TryToDeleteModel(Animal model) 
{
    if (model.IsOKToDelete())
    {
        DeleteModel(model)
    }
}

1
สิ่งนี้ไม่ย้ายตรรกะการลบไปที่โมเดลหรือไม่ จะดีกว่าไหมถ้าใช้วัตถุอื่นเพื่อจัดการกับสิ่งนี้ อาจเป็นโครงสร้างข้อมูลเช่น Dictionary <Cat> ภายใน ApplicationService ตรวจสอบเพื่อดูว่ามีแมวอยู่หรือไม่และหากเป็นเช่นนั้นแล้วให้ปิดการแจ้งเตือนการยืนยัน?
keelerjr12

@ keelerjr12 มันย้ายความรับผิดชอบในการพิจารณาว่าจำเป็นต้องมีการยืนยันสำหรับการลบลงในCatชั้นเรียน ฉันขอยืนยันว่านั่นคือที่ที่มันเป็น ไม่สามารถตัดสินใจได้ว่าการยืนยันนั้นจะสำเร็จได้อย่างไร (ที่ถูกแทรก) และไม่ลบตัวเอง ไม่เลยมันไม่ย้ายลอจิกลบในโมเดล
David Arno

2
ฉันรู้สึกว่าวิธีนี้จะนำไปสู่รหัสมากมายที่เกี่ยวข้องกับ UI ที่แนบมากับคลาสเอง หากคลาสมีจุดประสงค์เพื่อใช้ข้ามหลายเลเยอร์ UI ปัญหาจะเพิ่มขึ้น อย่างไรก็ตามถ้านี่เป็นคลาสชนิด ViewModel แทนที่จะเป็นเอนทิตีธุรกิจดูเหมือนว่าเหมาะสม
เกรแฮม

@Graham ใช่ว่าแน่นอนความเสี่ยงด้วยวิธีนี้: มันต้องอาศัยมันเป็นเรื่องง่ายที่จะฉีดเข้าไปในตัวอย่างของTellSceneToShowConfirmationAlert Catในสถานการณ์ที่ไม่ใช่เรื่องง่าย (เช่นในระบบหลายชั้นซึ่งการทำงานนี้อยู่ในระดับลึก) วิธีการนี้จะไม่ดี
David Arno

1
สิ่งที่ฉันได้รับ เอนทิตีธุรกิจเทียบกับคลาส ViewModel ในโดเมนธุรกิจ Cat ไม่ควรรู้เกี่ยวกับรหัสที่เกี่ยวข้องกับ UI แมวครอบครัวของฉันไม่เตือนใคร ขอบคุณ!
keelerjr12

1

ฉันอยากจะแนะนำให้ใช้รูปแบบของผู้เข้าชม ฉันใช้งานขนาดเล็กใน Java ฉันไม่คุ้นเคยกับ Swift แต่คุณสามารถปรับใช้ได้อย่างง่ายดาย

ผู้เยี่ยมชม

public interface AnimalVisitor<R>{
    R visitCat();
    R visitDog();
}

โมเดลของคุณ

abstract class Animal { // can also be an interface like VisitableAnimal
    abstract <R> R accept(AnimalVisitor<R> visitor);
}

class Cat extends Animal {
    public <R> R accept(AnimalVisitor<R> visitor) {
         return visitor.visitCat();
     }
}

class Dog extends Animal {
    public <R> R accept(AnimalVisitor<R> visitor) {
         return visitor.visitDog();
     }
}

โทรหาผู้เยี่ยมชม

public void tryToDelete(Animal animal) {
    animal.accept( new AnimalVisitor<Void>() {
        public Void visitCat() {
            tellSceneToShowConfirmation();
            return null;
        }

        public Void visitDog() {
            deleteModel(animal);
            return null;
        }
    });
}

คุณสามารถใช้งาน AnimalVisitor ได้มากเท่าที่คุณต้องการ

ตัวอย่าง:

public void isColorValid(Color color) {
    animal.accept( new AnimalVisitor<Boolean>() {
        public Boolean visitCat() {
            return Color.BLUE.equals(color);
        }

        public Boolean visitDog() {
            return true;
        }
    });
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.