เมื่อใดที่ enums ไม่มีกลิ่นรหัส


17

ภาวะที่กลืนไม่เข้าคายไม่ออก

ฉันได้อ่านหนังสือฝึกหัดที่ดีที่สุดมากมายเกี่ยวกับการฝึกฝนเชิงวัตถุและหนังสือเกือบทุกเล่มที่ฉันอ่านมีส่วนที่พวกเขาบอกว่า enums เป็นกลิ่นรหัส ฉันคิดว่าพวกเขาพลาดส่วนที่พวกเขาอธิบายเมื่อ enums ถูกต้อง

ดังนั้นฉันกำลังมองหาแนวทางและ / หรือกรณีการใช้งานที่ enums ไม่ได้กลิ่นรหัสและในความเป็นจริงโครงสร้างที่ถูกต้อง

แหล่งที่มา:

"คำเตือนตามกฎของหัวแม่มือ, enums เป็นรหัสที่มีกลิ่นและควรได้รับการ refactored เพื่อเรียน polymorphic [8]" Seemann, Mark, การฉีดพึ่งพาใน. Net, 2011, p. 342

[8] Martin Fowler et al., Refactoring: การปรับปรุงการออกแบบของรหัสที่มีอยู่ (นิวยอร์ก: Addison-Wesley, 1999), 82

บริบท

สาเหตุของภาวะที่กลืนไม่เข้าคายไม่ออกของฉันคือการซื้อขาย API พวกเขาให้กระแสข้อมูล Tick โดยส่งผ่านวิธีนี้:

void TickPrice(TickType tickType, double value)

ที่ไหน enum TickType { BuyPrice, BuyQuantity, LastPrice, LastQuantity, ... }

ฉันได้ลองทำ wrapper รอบ ๆ API นี้เพราะการแตกการเปลี่ยนแปลงเป็นวิถีชีวิตของ API นี้ ฉันต้องการติดตามมูลค่าของแต่ละประเภทที่ได้รับติ๊กบนเสื้อคลุมของฉันและฉันได้ทำโดยใช้พจนานุกรมของ ticktypes:

Dictionary<TickType,double> LastValues

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

มันง่ายที่จะหาสิ่งที่ไม่ต้องจ่ายเงิน แต่เป็นสิ่งที่ไม่ง่ายและฉันจะขอบคุณถ้าผู้คนสามารถแบ่งปันความเชี่ยวชาญข้อดีและข้อเสียของพวกเขา

ความคิดที่สอง

การตัดสินใจและการกระทำบางอย่างขึ้นอยู่กับสิ่งเหล่านี้TickTypeและฉันไม่สามารถนึกถึงวิธีที่จะกำจัดคำสั่ง enum / switch TickTypeวิธีการแก้ปัญหาที่สะอาดที่ฉันสามารถคิดจะใช้โรงงานและกลับไปดำเนินการบนพื้นฐานของ แม้ว่าฉันจะยังคงมีคำสั่ง switch ที่คืนค่าการใช้อินเตอร์เฟส

รายการด้านล่างเป็นหนึ่งในกลุ่มตัวอย่างที่ฉันสงสัยว่าฉันอาจใช้ผิด enum:

public class ExecutionSimulator
{
  Dictionary<TickType, double> LastReceived;
  void ProcessTick(TickType tickType, double value)
  {
    //Store Last Received TickType value
    LastReceived[tickType] = value;

    //Perform Order matching only on specific TickTypes
    switch(tickType)
    {
      case BidPrice:
      case BidSize:
        MatchSellOrders();
        break;
      case AskPrice:
      case AskSize:
        MatchBuyOrders();
        break;
    }       
  }
}

26
ฉันไม่เคยได้ยินว่า enums เป็นกลิ่นรหัส คุณสามารถใส่ข้อมูลอ้างอิงได้หรือไม่? ฉันคิดว่าพวกเขาเข้าใจถึงคุณค่าที่อาจเกิดขึ้นจำนวนมาก
Richard Tingle

25
หนังสืออะไรที่บอกว่า enums เป็นกลิ่นรหัส? รับหนังสือที่ดีกว่า
JacquesB

3
ดังนั้นคำตอบของคำถามจะเป็น "เวลาส่วนใหญ่"
gnasher729

5
เป็นค่าที่ดีพิมพ์ปลอดภัยที่สื่อถึงความหมาย? ที่รับประกันว่าจะใช้คีย์ที่เหมาะสมในพจนานุกรมหรือไม่ เมื่อไหร่จึงจะมีกลิ่นรหัส

7
ถ้าไม่มีบริบทฉันคิดว่าถ้อยคำที่ดีกว่าน่าจะเป็นenums as switch statements might be a code smell ...
pllee

คำตอบ:


31

Enums มีไว้สำหรับกรณีการใช้งานเมื่อคุณระบุค่าที่เป็นไปได้ทุกอย่างที่ตัวแปรใช้ เคย คิดกรณีการใช้งานเช่นวันของสัปดาห์หรือเดือนของปีหรือค่าการกำหนดค่าของการลงทะเบียนฮาร์ดแวร์ สิ่งที่มีทั้งความเสถียรสูงและสามารถแทนค่าได้ง่าย

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


3
นี่คือจุดที่สำคัญที่สุด - การเพิ่มมูลค่าพิเศษให้กับ enum หมายถึงการค้นหาการใช้งานทุกประเภทในรหัสของคุณและอาจจำเป็นต้องเพิ่มสาขาใหม่สำหรับค่าใหม่ เปรียบเทียบกับประเภท polymorphic ซึ่งการเพิ่มความเป็นไปได้ใหม่นั้นหมายถึงการสร้างคลาสใหม่ที่ใช้อินเทอร์เฟซการเปลี่ยนแปลงนั้นมีความเข้มข้นในที่เดียวทำให้ง่ายขึ้น หากคุณแน่ใจว่าจะไม่มีการเพิ่มประเภทเห็บใหม่ enum นั้นใช้ได้ไม่เช่นนั้นควรห่อในอินเตอร์เฟสแบบ polymorphic
จูลส์

นั่นเป็นหนึ่งในคำตอบที่ฉันต้องการ ฉันไม่สามารถหลีกเลี่ยงได้เมื่อวิธีที่ฉันห่อหุ้มใช้ enum และเป้าหมายคือการรวมศูนย์ enum เหล่านี้ไว้ในที่เดียว ฉันต้องทำให้เสื้อคลุมนั้นถ้าหาก API เปลี่ยน enums เหล่านี้ฉันต้องเปลี่ยนเสื้อคลุมไม่ใช่ผู้บริโภคของเสื้อคลุม
stromms

@Jules - "ถ้าคุณแน่ใจว่าจะไม่มีการเพิ่มประเภทเห็บใหม่ ... " คำสั่งนั้นผิดในหลายระดับ คลาสสำหรับทุก ๆ ประเภทแม้ว่าแต่ละประเภทจะมีพฤติกรรมที่เหมือนกัน แต่ก็ยังห่างไกลจากวิธีที่ "สมเหตุสมผล" เท่าที่จะทำได้ มันไม่ได้จนกว่าคุณจะมีพฤติกรรมที่แตกต่างและเริ่มต้องการคำสั่ง "สลับ / ถ้าแล้ว" ที่จะต้องวาดบรรทัด แน่นอนว่ามันไม่ได้ขึ้นอยู่กับว่าฉันจะเพิ่มคุณค่า enum มากขึ้นในอนาคตหรือไม่
Dunk

@dunk ฉันคิดว่าคุณเข้าใจผิดความคิดเห็นของฉัน ฉันไม่เคยแนะนำให้สร้างหลายประเภทที่มีพฤติกรรมเหมือนกัน - นั่นคงจะบ้า
จูลส์

1
แม้ว่าคุณจะ "ระบุทุกค่าที่เป็นไปได้" ซึ่งไม่ได้หมายความว่าประเภทนั้นจะไม่มีฟังก์ชันการทำงานเพิ่มเติมต่ออินสแตนซ์ แม้แต่บางอย่างเช่นเดือนก็มีคุณสมบัติที่มีประโยชน์อื่น ๆ เช่นจำนวนวัน การใช้ enum จะป้องกันไม่ให้แนวคิดที่คุณกำลังสร้างแบบจำลองขยายออกไปในทันที มันเป็นกลไก / รูปแบบการต่อต้าน OOP
Dave Cousineau

17

ประการแรกกลิ่นรหัสไม่ได้หมายความว่ามีบางอย่างผิดปกติ หมายความว่ามีบางอย่างผิดปกติ enumมีกลิ่นเนื่องจากมีการใช้งานในทางที่ผิดบ่อยครั้ง แต่นั่นไม่ได้หมายความว่าคุณต้องหลีกเลี่ยง เพียงคุณพบว่าตัวเองพิมพ์enumหยุดและตรวจสอบวิธีแก้ไขปัญหาที่ดีกว่า

กรณีเฉพาะที่เกิดขึ้นบ่อยที่สุดคือเมื่อ enum ที่แตกต่างกันตรงกับประเภทที่แตกต่างกับพฤติกรรมที่แตกต่าง แต่อินเทอร์เฟซเดียวกัน ตัวอย่างเช่นการพูดคุยกับแบ็กเอนด์ที่แตกต่างกันการแสดงผลหน้าต่างๆและอื่น ๆ เหล่านี้มีการใช้งานอย่างเป็นธรรมชาติมากขึ้นโดยใช้คลาส polymorphic

ในกรณีของคุณ TickType ไม่สอดคล้องกับพฤติกรรมที่แตกต่าง เป็นเหตุการณ์ประเภทต่าง ๆ หรือคุณสมบัติต่าง ๆ ของสถานะปัจจุบัน ดังนั้นฉันคิดว่านี่เป็นสถานที่ที่เหมาะสำหรับ enum


คุณกำลังบอกว่าเมื่อ enums มีคำนามเป็นรายการ (เช่นสี) พวกเขาอาจใช้อย่างถูกต้อง และเมื่อพวกเขาเป็นคำกริยา (เชื่อมต่อแสดงผล) แล้วมันเป็นสัญญาณว่ามันถูกใช้ในทางที่ผิด?
stromms

2
@stromms ไวยากรณ์เป็นแนวทางที่น่ากลัวสำหรับสถาปัตยกรรมซอฟต์แวร์ หาก TickType เป็นอินเตอร์เฟสแทน enum วิธีการจะเป็นอย่างไร ฉันไม่เห็นเลยนั่นเป็นเหตุผลว่าทำไมมันถึงเป็นคดีของฉัน กรณีอื่น ๆ เช่นสถานะสีแบ็กเอนด์ ฯลฯ ฉันสามารถหาวิธีได้และนั่นเป็นเหตุผลว่าทำไมพวกเขาถึงเป็นอินเทอร์เฟซ
Winston Ewert

@WinstonEwert และด้วยการแก้ไขปรากฏว่าพวกเขามีพฤติกรรมที่แตกต่างกัน

Ohhh ดังนั้นถ้าฉันสามารถคิดวิธีการ enum แล้วมันอาจจะเป็นผู้สมัครสำหรับอินเทอร์เฟซ? นั่นอาจเป็นจุดที่ดีมาก
stromms

1
@stromms คุณสามารถแบ่งปันตัวอย่างสวิตช์เพิ่มเติมได้ดังนั้นฉันจะได้รับแนวคิดที่ดีขึ้นว่าคุณใช้ TickType ได้อย่างไร
Winston Ewert

8

เมื่อส่งข้อมูล enums ไม่มีกลิ่นรหัส

IMHO เมื่อส่งข้อมูลโดยใช้enumsเพื่อระบุว่าเขตข้อมูลสามารถมีค่าจากชุดของค่าที่ถูก จำกัด (ไม่ค่อยมีการเปลี่ยนแปลง) เป็นสิ่งที่ดี

ฉันคิดว่ามันกว่าที่จะส่งสัญญาณโดยพลการหรือstrings intsสายอักขระอาจทำให้เกิดปัญหาจากการสะกดคำและตัวพิมพ์ใหญ่ ints อนุญาตให้มีการส่งออกของค่าช่วงและมีความหมายเล็ก ๆ น้อย ๆ (เช่นผมได้รับ3จากการให้บริการการซื้อขายของคุณมันหมายความว่าอะไร? LastPrice? LastQuantity? สิ่งอื่นใด

การส่งวัตถุและการใช้ลำดับชั้นของคลาสนั้นไม่สามารถทำได้ เช่นไม่อนุญาตให้จุดสิ้นสุดการรับแยกความแตกต่างของคลาสที่ถูกส่ง

ในโครงการของฉันบริการใช้ลำดับชั้นของคลาสสำหรับเอฟเฟกต์ของการดำเนินการก่อนที่จะส่งผ่านDataContractวัตถุจากลำดับชั้นของคลาสจะถูกคัดลอกลงในunionวัตถุที่เหมือนซึ่งมีการenumระบุประเภท ลูกค้าได้รับDataContractและสร้างวัตถุของคลาสในลำดับชั้นโดยใช้enumค่าswitchและสร้างวัตถุประเภทที่ถูกต้อง

อีกเหตุผลหนึ่งที่คนไม่ต้องการส่งออบเจ็กต์ของคลาสคือบริการอาจต้องการพฤติกรรมที่แตกต่างไปจากเดิมอย่างสิ้นเชิงสำหรับวัตถุที่ส่ง (เช่นLastPrice) กว่าไคลเอนต์ ในกรณีนั้นการส่งคลาสและวิธีการไม่พึงประสงค์

งบสวิตช์ไม่ดีหรือไม่?

IMHO ที่เดียว switch -statement ที่เรียกก่อสร้างงานแตกต่างกันขึ้นอยู่กับการenumไม่ได้เป็นกลิ่นรหัส มันไม่จำเป็นต้องดีกว่าหรือแย่กว่าวิธีอื่น ๆ เช่นการสะท้อนจากชื่อพิมพ์ ขึ้นอยู่กับสถานการณ์จริง

มีสวิตช์ในenumทุกที่เป็นกลิ่นรหัสให้ทางเลือกที่มักจะดีกว่า:

  • ใช้วัตถุของคลาสที่แตกต่างกันของลำดับชั้นที่มีวิธีการแทนที่ ความหลากหลายคือ
  • ใช้รูปแบบผู้เยี่ยมชมเมื่อชั้นเรียนในลำดับชั้นไม่ค่อยมีการเปลี่ยนแปลงและเมื่อใดที่การดำเนินการ (มาก) ควรจะถูกผนวกเข้ากับชั้นเรียนในลำดับชั้นอย่างหลวม ๆ

ที่จริง TickType intจะถูกส่งผ่านเส้นลวดในฐานะที่เป็น เสื้อคลุมของฉันคือคนที่ใช้ซึ่งเป็นที่หล่อจากที่ได้รับTickType intมีหลายเหตุการณ์ที่ใช้ ticktype นี้พร้อมด้วยลายเซ็นที่แตกต่างกันอย่างรวดเร็วซึ่งเป็นการตอบสนองต่อคำขอต่างๆ เป็นเรื่องปกติไหมที่จะใช้intฟังก์ชั่นต่าง ๆ อย่างต่อเนื่อง? เช่นTickPrice(int type, double value)ใช้ 1,3 และ 6 typeในขณะที่TickSize(int type, double valueใช้ 2,4 และ 5? มันสมเหตุสมผลไหมที่จะแยกสิ่งเหล่านั้นออกเป็นสองเหตุการณ์?
stromms

2

ถ้าคุณไปด้วยประเภทที่ซับซ้อนมากขึ้น:

    abstract class TickType
    {
      public abstract string Name {get;}
      public abstract double TickValue {get;}
    }

    class BuyPrice : TickType
    {
      public override string Name { get { return "Buy Price"; } }
      public override double TickValue { get { return 2.35d; } }
    }

    class BuyQuantity : TickType
    {
      public override string Name { get { return "Buy Quantity"; } }
      public override double TickValue { get { return 4.55d; } }
    }
//etcetera

จากนั้นคุณสามารถโหลดประเภทของคุณจากการไตร่ตรองหรือสร้างมันขึ้นมาเอง แต่สิ่งที่สำคัญที่สุดที่จะเกิดขึ้นคือคุณกำลังถือหลักการเปิดปิดของโซลิด


1

TL; DR

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

ขอโทษด้วยเมื่อฉันสร้างรหัสดั้งเดิมขึ้นอีกครั้ง / ถอนหายใจ; ^ D


แต่ ... ทำไม

ตรวจสอบที่ดีงามว่าทำไมเป็นกรณีจาก LosTechies ที่นี่ :

// calculate the service fee
public double CalculateServiceFeeUsingEnum(Account acct)
{
    double totalFee = 0;
    foreach (var service in acct.ServiceEnums) { 

        switch (service)
        {
            case ServiceTypeEnum.ServiceA:
                totalFee += acct.NumOfUsers * 5;
                break;
            case ServiceTypeEnum.ServiceB:
                totalFee += 10;
                break;
        }
    }
    return totalFee;
} 

สิ่งนี้มีปัญหาเหมือนกับรหัสด้านบนทั้งหมด เมื่อแอพพลิเคชั่นใหญ่ขึ้นโอกาสที่จะมีคำสั่งสาขาคล้ายกันเพิ่มขึ้น นอกจากนี้เมื่อคุณเปิดตัวบริการพรีเมียมเพิ่มเติมคุณจะต้องแก้ไขรหัสนี้อย่างต่อเนื่องซึ่งเป็นการละเมิดหลักการ Open-Closed มีปัญหาอื่น ๆ ที่นี่เช่นกัน ฟังก์ชั่นในการคำนวณค่าบริการไม่จำเป็นต้องทราบจำนวนเงินที่แท้จริงของแต่ละบริการ นั่นคือข้อมูลที่จำเป็นต้องได้รับการห่อหุ้ม

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

เราจะทำการ refactor เพื่อใช้พฤติกรรม polymorphic สิ่งที่เราต้องการคือสิ่งที่เป็นนามธรรมซึ่งจะช่วยให้เรามีพฤติกรรมที่จำเป็นในการคำนวณค่าธรรมเนียมสำหรับบริการ

public interface ICalculateServiceFee
{
    double CalculateServiceFee(Account acct);
}

...

ตอนนี้เราสามารถสร้างอินเทอร์เฟซที่ใช้งานได้อย่างเป็นรูปธรรมและแนบ [กับ] บัญชี

public class Account{
    public int NumOfUsers{get;set;}
    public ICalculateServiceFee[] Services { get; set; }
} 

public class ServiceA : ICalculateServiceFee
{
    double feePerUser = 5; 

    public double CalculateServiceFee(Account acct)
    {
        return acct.NumOfUsers * feePerUser;
    }
} 

public class ServiceB : ICalculateServiceFee
{
    double serviceFee = 10;
    public double CalculateServiceFee(Account acct)
    {
        return serviceFee;
    }
} 

อีกกรณีการใช้งาน ...

บรรทัดล่างคือถ้าคุณมีพฤติกรรมที่ขึ้นอยู่กับค่า enum ทำไมไม่มีการใช้งานที่แตกต่างกันของอินเตอร์เฟซที่คล้ายกันหรือระดับผู้ปกครองที่ช่วยให้มั่นใจว่าค่าที่มีอยู่? ในกรณีของฉันฉันกำลังดูข้อความแสดงข้อผิดพลาดต่าง ๆ ตามรหัสสถานะ REST แทน...

private static string _getErrorCKey(int statusCode)
{
    string ret;

    switch (statusCode)
    {
        case StatusCodes.Status403Forbidden:
            ret = "BRANCH_UNAUTHORIZED";
            break;

        case StatusCodes.Status422UnprocessableEntity:
            ret = "BRANCH_NOT_FOUND";
            break;

        default:
            ret = "BRANCH_GENERIC_ERROR";
            break;
    }

    return ret;
}

... บางทีฉันควรห่อรหัสสถานะในชั้นเรียน

public interface IAmStatusResult
{
    int StatusResult { get; }    // Pretend an int's okay for now.
    string ErrorKey { get; }
}

จากนั้นทุกครั้งที่ฉันต้องการ IAmStatusResult รูปแบบใหม่ฉันโค้ดมัน ...

public class UnauthorizedBranchStatusResult : IAmStatusResult
{
    public int StatusResult => 403;
    public string ErrorKey => "BRANCH_UNAUTHORIZED";
}

... และตอนนี้ฉันสามารถมั่นใจได้ว่าได้ตระหนักถึงรหัสก่อนหน้านี้มันมีIAmStatusResultอยู่ในขอบเขตและการอ้างอิงของมันแทนการที่ซับซ้อนมากขึ้นหมดหนทางentity.ErrorKey_getErrorCode(403)

และที่สำคัญกว่าทุกครั้งที่ฉันจะเพิ่มรูปแบบใหม่ของค่าตอบแทนที่ไม่มีความต้องการรหัสอื่น ๆ ที่จะเพิ่มจะจัดการกับมัน Whaddya รู้enumและswitchมีแนวโน้มว่ารหัสมีกลิ่น

กำไร.


แล้วกรณีที่เช่นฉันมีคลาส polymorphic และฉันต้องการกำหนดค่าที่ฉันใช้ผ่านพารามิเตอร์บรรทัดคำสั่ง บรรทัดคำสั่ง -> enum -> สวิตช์ / แผนที่ -> การใช้งานดูเหมือนจะเป็นกรณีการใช้งานที่ถูกกฎหมาย ฉันคิดว่าสิ่งสำคัญคือการใช้ enum สำหรับ orchestration ไม่ใช่การใช้ตรรกะตามเงื่อนไข
Ant P

@AntP ทำไมไม่ในกรณีนี้ใช้StatusResultคุ้มค่า? คุณสามารถยืนยันว่า Enum นั้นมีประโยชน์ในฐานะทางลัดที่น่าจดจำของมนุษย์ในกรณีที่ใช้ แต่ฉันอาจจะยังคงเรียกว่ากลิ่นรหัสเพราะมีทางเลือกที่ดีที่ไม่ต้องการคอลเลกชันที่ปิด
ruffin

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

@AntP ฉันไม่แน่ใจว่ามีสายไฟที่นี่หรือไม่ หากคุณอ้างอิงคุณสมบัติบนอินเทอร์เฟซคุณสามารถใช้งานอินเทอร์เฟซนั้นได้ทุกที่ที่ต้องการและไม่อัปเดตรหัสนั้นเมื่อคุณสร้างการใช้งาน (หรือลบที่มีอยู่) ใหม่ของอินเทอร์เฟซนั้น หากคุณมีenumคุณจะต้องอัปเดตรหัสทุกครั้งที่คุณเพิ่มหรือลบค่าและอาจมีหลายแห่งขึ้นอยู่กับว่าคุณจัดโครงสร้างรหัสของคุณอย่างไร ใช้ polymorphism แทน enum เป็น sorta เช่นการสร้างความมั่นใจรหัสของคุณอยู่ในBoyce-Codd ปกติฟอร์ม
ruffin

ใช่. ทีนี้สมมติว่าคุณมีการนำโพลีมอร์ฟิคมาใช้เช่นกัน ในบทความ Los Techies พวกเขาทำซ้ำผ่านพวกเขาทั้งหมด แต่ถ้าฉันต้องการใช้การดำเนินการเพียงครั้งเดียวตามเงื่อนไขเช่นพารามิเตอร์บรรทัดคำสั่ง ตอนนี้เราต้องกำหนดการแมปจากค่าการกำหนดค่าบางอย่างให้กับการใช้งานแบบฉีดได้ enum ในกรณีนี้ดีพอ ๆ กับการกำหนดค่าประเภทอื่น นี่เป็นตัวอย่างของสถานการณ์ที่ไม่มีโอกาสเพิ่มเติมที่จะแทนที่ "สกปรก enum" ด้วยความหลากหลาย สาขาโดยสิ่งที่เป็นนามธรรมเท่านั้นที่สามารถพาคุณไปไกล
Ant P

0

ไม่ว่าการใช้ enum จะเป็นกลิ่นรหัสหรือไม่นั้นขึ้นอยู่กับบริบท ฉันคิดว่าคุณจะได้รับความคิดบางอย่างสำหรับการตอบคำถามของคุณถ้าคุณพิจารณาปัญหาการแสดงออก ดังนั้นคุณมีคอลเลกชันประเภทต่าง ๆ และคอลเลกชันของการดำเนินการเหล่านั้นและคุณจำเป็นต้องจัดระเบียบรหัส มีสองตัวเลือกง่าย ๆ :

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

ทางออกไหนดีกว่ากัน?

หาก Karl Bielefeldt ชี้ให้เห็นว่าประเภทของคุณได้รับการแก้ไขและคุณคาดหวังว่าระบบจะเติบโตเป็นหลักโดยการเพิ่มการดำเนินการใหม่ในประเภทเหล่านี้จากนั้นใช้ enum และการเปลี่ยนคำสั่งเป็นทางออกที่ดีกว่า: ทุกครั้งที่คุณต้องการปฏิบัติการใหม่ เพียงใช้ขั้นตอนใหม่โดยใช้คลาสคุณจะต้องเพิ่มวิธีในแต่ละชั้นเรียน

ในทางกลับกันหากคุณคาดว่าจะมีชุดการทำงานที่ค่อนข้างเสถียร แต่คุณคิดว่าคุณจะต้องเพิ่มประเภทข้อมูลมากขึ้นเมื่อเวลาผ่านไปการใช้โซลูชันเชิงวัตถุจะสะดวกกว่า: เมื่อต้องใช้ชนิดข้อมูลใหม่คุณเพียงแค่ เพิ่มคลาสใหม่ที่ใช้อินเทอร์เฟซเดียวกันอยู่เรื่อย ๆ ในขณะที่ถ้าคุณใช้ enum อยู่คุณจะต้องอัพเดตคำสั่ง switch ทั้งหมดในโพรซีเดอร์ทั้งหมดโดยใช้ enum

หากคุณไม่สามารถจำแนกปัญหาของคุณด้วยตัวเลือกใดตัวเลือกหนึ่งจากสองตัวเลือกด้านบนคุณสามารถดูวิธีแก้ปัญหาที่ซับซ้อนมากขึ้น (ดูตัวอย่างหน้า Wikipedia ที่กล่าวถึงข้างต้นอีกครั้ง สำหรับการอภิปรายสั้น ๆ และการอ้างอิงเพื่อการอ่านเพิ่มเติม)

ดังนั้นคุณควรพยายามเข้าใจว่าแอปพลิเคชั่นของคุณพัฒนาไปในทิศทางใดแล้วเลือกวิธีที่เหมาะสม

เนื่องจากหนังสือที่คุณอ้างถึงเกี่ยวข้องกับกระบวนทัศน์เชิงวัตถุจึงไม่น่าแปลกใจที่หนังสือเหล่านี้มีอคติต่อการใช้ enums อย่างไรก็ตามการแก้ปัญหาการวางแนววัตถุไม่ใช่ตัวเลือกที่ดีที่สุดเสมอไป

Bottomline: enums ไม่จำเป็นต้องเป็นกลิ่นรหัส


โปรดทราบว่าคำถามถูกถามในบริบทของ C # ซึ่งเป็นภาษา OOP นอกจากนี้การจัดกลุ่มตามการดำเนินการหรือตามประเภทเป็นสองด้านของเหรียญเดียวกันน่าสนใจ แต่ฉันไม่เห็นว่า "ดีกว่า" กว่าอีกอย่างที่คุณอธิบาย จำนวนผลลัพธ์ของรหัสจะเท่ากันและภาษาของ OOP จะช่วยในการจัดการตามประเภท มันยังสร้างโค้ดที่ใช้งานง่ายขึ้น (อ่านง่าย) ได้อีกด้วย
Dave Cousineau เมื่อ

@DaveCousineau: "ผมไม่เห็นว่าเป็น 'ดี' กว่าที่อื่น ๆ ตามที่คุณอธิบาย" ผมไม่ได้บอกว่าใครอยู่เสมอดีกว่าอื่น ๆ ฉันบอกว่าอันหนึ่งดีกว่าอีกอันหนึ่งขึ้นอยู่กับบริบท เช่นถ้าการดำเนินงานจะมากหรือน้อยคงที่และคุณวางแผนที่จะเพิ่มรูปแบบใหม่ (เช่น GUI กับการแก้ไขpaint(), show(), close() resize()การดำเนินงานและเครื่องมือที่กำหนดเอง) แล้ววิธีการเชิงวัตถุจะดีกว่าในแง่ที่ว่ามันช่วยให้การเพิ่มรูปแบบใหม่โดยไม่มีผลกระทบมากเกินไป รหัสที่มีอยู่มาก (โดยทั่วไปคุณใช้คลาสใหม่ซึ่งเป็นการเปลี่ยนแปลงแบบโลคัล)
Giorgio

ฉันไม่เห็นว่า "ดีขึ้น" อย่างที่คุณอธิบายความหมายว่าฉันไม่เห็นว่ามันขึ้นอยู่กับสถานการณ์ จำนวนรหัสที่คุณต้องเขียนนั้นเท่ากันทั้งสองสถานการณ์ ข้อแตกต่างเพียงอย่างเดียวคือวิธีการหนึ่งตรงกันข้ามกับ OOP (enums) และไม่ใช่
Dave Cousineau

@DaveCousineau: จำนวนของรหัสที่คุณต้องเขียนนั้นอาจจะเท่ากันสถานที่ (สถานที่ในซอร์สโค้ดที่คุณต้องเปลี่ยนและคอมไพล์ใหม่) จะเปลี่ยนไปอย่างมาก เช่นใน OOP หากคุณเพิ่มวิธีการใหม่ในส่วนต่อประสานคุณจะต้องเพิ่มการใช้งานสำหรับแต่ละคลาสที่ใช้ส่วนต่อประสานนั้น
Giorgio

มันไม่ได้เป็นแค่เรื่องของความลึกของ VS การเพิ่ม 1 วิธีใน 10 ไฟล์หรือ 10 วิธีใน 1 ไฟล์
Dave Cousineau
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.