ข้ามไปยังเนื้อหา
Interface คืออะไร? ตัวอย่าง C# เปลี่ยนกติกาค่าขนส่ง

บทความโดย: 9Expert Team

Interface คืออะไร? ตัวอย่าง C# เปลี่ยนกติกาค่าขนส่ง

Interface คืออะไรใน C# ต่างจาก Class อย่างไร เรียนผ่านตัวอย่างค่าขนส่งสองแบบ พร้อมผลลัพธ์ จุดทดสอบ และแนวทางเลือกใช้ให้เหมาะกับงาน

9Expert Team 2 นาที

Interface คืออะไร? ใน C# Interface ใช้กำหนดสัญญาความสามารถที่ชนิดข้อมูลต้องรองรับ เช่น การคำนวณค่าขนส่ง ส่วน Class ที่ implement เป็นผู้กำหนดวิธีทำงานจริง ช่วยให้เปลี่ยน implementation ได้โดยโค้ดผู้เรียกยังใช้สัญญาเดิม

Interface ต่างจาก Class อย่างไร

Class ใช้สร้างชนิดข้อมูลที่มีสถานะและการทำงาน ส่วน Interface เน้นสัญญาที่ผู้ใช้โค้ดเรียกได้ ตัวแปรชนิด Interface สามารถอ้างถึง Object ของ Class ที่ implement สัญญานั้น แต่ไม่สร้าง instance ของ Interface ด้วย new ตรง ๆ

แนวคิด

ตัวอย่างในบทนี้

Interface

IShippingFee กำหนด Calculate(decimal)

Implementation

FlatShipping และ FreeOver500Shipping

ผู้เรียกใช้งาน

Checkout เรียกผ่าน IShippingFee

การเปลี่ยนพฤติกรรม

ส่ง implementation คนละตัวเข้า constructor

ตามเอกสาร C# Interfaces Class หรือ Struct รองรับได้หลาย interface ขณะที่ C# ไม่รองรับสืบทอดจากหลาย Class พร้อมกัน Interface สมัยใหม่มีความสามารถมากกว่ารายการ method ว่าง เช่น default implementations จึงไม่ควรสรุปว่า Interface ห้ามมี implementation ทุกกรณี บทนี้ใช้รูปแบบพื้นฐานเพื่อให้เห็นสัญญาชัดเจน

ตัวอย่าง: เปลี่ยนกติกาค่าขนส่งโดยไม่แก้ Checkout

สมมติมีค่าขนส่งปกติ 30 บาท และโปรโมชันส่งฟรีเมื่อยอดสินค้าตั้งแต่ 500 บาท สร้าง Console App แล้วแทน Program.cs ทั้งไฟล์ด้วยตัวอย่างนี้ ไม่รวมภาษี ส่วนลด หรือการเรียกบริการภายนอก

using System;

public interface IShippingFee
{
    decimal Calculate(decimal orderTotal);
}

public class FlatShipping : IShippingFee
{
    public decimal Calculate(decimal orderTotal) { return 30m; }
}

public class FreeOver500Shipping : IShippingFee
{
    public decimal Calculate(decimal orderTotal)
    {
        return orderTotal >= 500m ? 0m : 30m;
    }
}

public class Checkout
{
    private readonly IShippingFee _shipping;
    public Checkout(IShippingFee shipping) { _shipping = shipping; }
    public decimal Total(decimal orderTotal)
    {
        if (orderTotal < 0) throw new ArgumentOutOfRangeException(nameof(orderTotal));
        return orderTotal + _shipping.Calculate(orderTotal);
    }
}

public static class Program
{
    public static void Main()
    {
        var regular = new Checkout(new FlatShipping());
        var promotion = new Checkout(new FreeOver500Shipping());
        Console.WriteLine($"Regular 500: {regular.Total(500m):0}");
        Console.WriteLine($"Promotion 500: {promotion.Total(500m):0}");
        Console.WriteLine($"Promotion 499: {promotion.Total(499m):0}");
    }
}

ผลลัพธ์ที่ควรได้

Regular 500: 530
Promotion 500: 500
Promotion 499: 529

Checkout ไม่ต้องรู้ว่าค่าขนส่งมาจากกติกาใด เพราะเรียก Calculate ตามสัญญาเดียวกัน การส่ง implementation ผ่าน constructor ในตัวอย่างเป็นการจัด dependency ด้วยมือ ยังไม่จำเป็นต้องใช้ DI container เพื่อเข้าใจแนวคิด

อ่านโค้ดทีละส่วน

  1. IShippingFee ระบุชนิด input และค่าที่คืน แต่ไม่ได้บอกผู้เรียกว่าคำนวณด้วยสูตรใด

  2. FlatShipping คืนค่าคงที่ ส่วน FreeOver500Shipping ตรวจยอดเทียบ 500

  3. Checkout รับ IShippingFee และใช้มันใน Total จึงไม่ต้องเพิ่ม if แยกชื่อ Class ทุกครั้ง

  4. Program เลือกกติกาตอนสร้าง Object ผู้เรียกใช้เมธอด Total ในรูปแบบเดียวกัน

เกณฑ์ตรวจรับและจุดขอบ

ทดสอบ

ผลที่ควรได้

ยอด 499 กติกาโปรโมชัน

529

ยอด 500 กติกาโปรโมชัน

500

ยอด 501 กติกาโปรโมชัน

501

ยอดติดลบผ่าน Checkout

ArgumentOutOfRangeException

เปลี่ยนเป็น FlatShipping ยอด 501

531

หากกติกาจริงอนุญาตค่าขนส่งติดลบไม่ได้ ต้องออกแบบ validation เพิ่มในชั้นที่เหมาะสม สัญญาทางภาษาเพียงอย่างเดียวไม่รับรองว่า implementation จะทำตามกฎธุรกิจทั้งหมด อ่านException ใน .NET เพื่อกำหนดการรับมือกรณีผิดเงื่อนไข

ใช้ Interface เมื่อใด

เหมาะเมื่อมีหลาย implementation, ต้องแยกส่วนที่เปลี่ยนบ่อย หรืออยากแทนบริการภายนอกด้วยตัวทดสอบ แต่ไม่จำเป็นต้องสร้าง Interface ให้ทุก Class โดยไม่มีเหตุผล เพราะเพิ่มสิ่งที่ต้องดูแล ควรตั้งชื่อจากความสามารถ ไม่ตั้งให้กว้างจนรวมทุกเรื่อง

ศึกษาต่อเรื่องInterface กับ Multiple Inheritance และOOP with C# โดยแยกตัวอย่างพื้นฐานออกจากรายละเอียดเฉพาะรุ่นในบทเก่า

คำถามที่พบบ่อย

Interface เป็นหน้าจอผู้ใช้ไหม?

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

Implement แล้วข้อมูลของ Object เหมือนกันไหม?

ไม่จำเป็น Object ต่าง Class มีสถานะและวิธีทำงานต่างกันได้ แต่ต้องรองรับสมาชิกที่สัญญากำหนด

มีเพียง implementation เดียว ใช้ได้หรือไม่?

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

หลักสูตรที่เกี่ยวข้อง

Programming in C# with Visual Studio · ASP.NET Core MVC · ASP.NET Core Web API & Security · .NET MAUI : การพัฒนา Native Cross-platform Apps ด้วย C#

เลือกเส้นทางเรียนตามพื้นฐานและหน้าที่งาน ตรวจรายละเอียดหลักสูตรก่อนสมัคร ไม่ได้หมายความว่าทุกหลักสูตรครอบคลุมทุกฟีเจอร์ในบทนี้

หลักสูตรที่เกี่ยวข้อง

บทความที่เกี่ยวข้อง

การเขียนโปรแกรมแบบวัตถุวิธีในภาษาซีชาร์ป (OOP with C#) ตอนที่ 4

การเขียนโปรแกรมแบบวัตถุวิธีในภาษาซีชาร์ป (OOP with C#) ตอนที่ 4

ในตอนที่ผ่านมาผู้เขียนได้พูดถึงพรอพเพอร์ตี (property) โดยละเอียด และได้พาดพิงถึงหลักการที่เกี่ยวข้องกับคลาสสิกพรอพเพอร์ตี อาทิ สมาชิกแบบฟิลด์, เอนเคปซูเลชัน, การเชื่อมหลวมและเกตเตอร์/เซตเตอร์ ในบทความตอนนี้ผู้เขียนจะพูดถึงแง่มุมต่าง ๆ ของการเขียนโค้ดใช้งานพรอพเพอร์ตี เช่น วิธีใส่โค้ดคัดกรองข้อมูลในคลาสสิกพรอพเพอร์ตี วิธีเขียนและใช้งาน “ออโตพรอพเพอร์ตี” (auto implemented properties) และวิธีลดทอนพรอพเพอร์ตีด้วย “นิพจน์ฝังตัว” (Expression-bodied members) ที่มีประโยชน์มาก เพราะจะช่วยให้การเขียนโค้ดกระชับขึ้นไปอีก

อ่านเพิ่มเติม