C#观察者模式(Observer Pattern)实现正确性验证及总金额计算咨询
Hey there! Great job getting started with the Observer Pattern in C#—your core implementation hits the mark, and it’s clear you grasp how subjects and observers interact. Let’s break down what’s working well, some tweaks to make it more idiomatic .NET, and how to add total amount calculation.
First off, your approach is correct: Products acts as the subject maintaining an event, while Shipping, Discount, and Tax are observers that subscribe to react when a product is added. Here are some improvements to align with .NET best practices:
1. Use .NET's Built-in EventHandler<T> Delegate
You don’t need to define a custom ProductAddedEventHandler—.NET provides a generic EventHandler<TEventArgs> delegate that’s standard for events. Swap out your custom delegate with this:
public event EventHandler<ProductAddedEventArgs> ProductAdded;
2. Simplify Event Invocation
Instead of checking if (ProductAdded != null) before invoking, use the null-conditional operator ?.Invoke for cleaner, safer code:
ProductAdded?.Invoke(this, new ProductAddedEventArgs(subtotal));
3. Rename Methods for Clarity
Names like create are vague—rename methods to reflect their actual purpose. For example:
Calculate.create→Calculate.CalculateSubtotalShipping.create→Shipping.CalculateShippingDiscount.create→Discount.CalculateDiscount
This makes your code much more readable for anyone else (or future you!) looking at it.
4. Optimize Product Collection Management
Right now, every time you add a product, you fetch the entire list from ProductModels and concatenate a new collection. If ProductModels.Products() pulls from a database or external source, this will get slow quickly. Instead, maintain an internal list in the Products class:
private List<Product> _products = new List<Product>(); public void Add(Product product) { _products.Add(product); this.subtotal = new Calculate().CalculateSubtotal(_products); // Trigger event... }
Total amount logic depends on your business rules, but a common formula is:Total = (Subtotal - Discount) + Shipping + Tax
(Note: You might adjust whether tax applies to the discounted amount or subtotal—tweak based on your needs.)
Here are two clean ways to implement this:
Option 1: Add a TotalCalculator Observer
Create a new observer class that handles total calculation, just like your existing shipping/discount/tax classes. This keeps each observer focused on a single responsibility (following the Single Responsibility Principle):
public class TotalCalculator { const double ShippingRate = 0.05; const double DiscountRate = 0.2; const double TaxRate = 0.07; public void CalculateTotal(object sender, ProductAddedEventArgs e) { double subtotal = e.SubTotal; double discount = subtotal * DiscountRate; double discountedSubtotal = subtotal - discount; double shipping = subtotal * ShippingRate; // Or use discountedSubtotal if needed double tax = discountedSubtotal * TaxRate; double total = discountedSubtotal + shipping + tax; Console.WriteLine("Total Amount: {0:F2}", total); } }
Then subscribe it in your Main method:
TotalCalculator totalCalc = new TotalCalculator(); products.ProductAdded += totalCalc.CalculateTotal;
Option 2: Extend the Event Args (If Needed)
If you want observers to share calculated values (e.g., let TotalCalculator use the discount amount calculated by Discount), you could extend ProductAddedEventArgs to include those values. But this complicates the pattern—Option 1 is cleaner since each observer calculates its own value independently.
Here’s your code with all the tweaks and total calculation added:
using System; using System.Collections.Generic; using System.Linq; class Program { // Assuming you have this Product class defined (you omitted it in your snippet) public class Product { public int Id { get; set; } public string Name { get; set; } public double Price { get; set; } } public class Products { private List<Product> _products = new List<Product>(); private double _subtotal; // Use .NET's standard event delegate public event EventHandler<ProductAddedEventArgs> ProductAdded; public void Add(Product product) { _products.Add(product); var calculator = new Calculate(); _subtotal = calculator.CalculateSubtotal(_products); // Simplified event invocation ProductAdded?.Invoke(this, new ProductAddedEventArgs(_subtotal)); Console.WriteLine("Subtotal Amount: {0:F2}", _subtotal); } } /// <summary> /// Calculates the subtotal of a product list /// </summary> public class Calculate { public double CalculateSubtotal(IEnumerable<Product> products) { double result = 0; foreach (var product in products) { result += product.Price; } return result; } } public class ProductAddedEventArgs : EventArgs { public double SubTotal { get; } public ProductAddedEventArgs(double subtotal) => SubTotal = subtotal; } public class Shipping { const double ShippingRate = 0.05; public void CalculateShipping(object sender, ProductAddedEventArgs e) { Console.WriteLine("Shipping Amount: {0:F2}", e.SubTotal * ShippingRate); } } public class Discount { const double DiscountRate = 0.2; public void CalculateDiscount(object sender, ProductAddedEventArgs e) { Console.WriteLine("Discount Amount: {0:F2}", e.SubTotal * DiscountRate); } } public class Tax { const double TaxRate = 0.07; public void CalculateTax(object sender, ProductAddedEventArgs e) { // Calculate tax on discounted amount (adjust if needed) double discountedSubtotal = e.SubTotal - (e.SubTotal * 0.2); Console.WriteLine("Tax Amount: {0:F2}", discountedSubtotal * TaxRate); } } public class TotalCalculator { const double ShippingRate = 0.05; const double DiscountRate = 0.2; const double TaxRate = 0.07; public void CalculateTotal(object sender, ProductAddedEventArgs e) { double subtotal = e.SubTotal; double discount = subtotal * DiscountRate; double discountedSubtotal = subtotal - discount; double shipping = subtotal * ShippingRate; double tax = discountedSubtotal * TaxRate; double total = discountedSubtotal + shipping + tax; Console.WriteLine("Total Amount: {0:F2}", total); } } static void Main(string[] args) { var products = new Products(); var shipping = new Shipping(); var discount = new Discount(); var tax = new Tax(); var totalCalc = new TotalCalculator(); // Subscribe all observers products.ProductAdded += shipping.CalculateShipping; products.ProductAdded += discount.CalculateDiscount; products.ProductAdded += tax.CalculateTax; products.ProductAdded += totalCalc.CalculateTotal; products.Add(new Product { Id = 50, Name = "Watermelon", Price = 9.21 }); } }
When you run this, you’ll get output like:
Subtotal Amount: 9.21 Shipping Amount: 0.46 Discount Amount: 1.84 Tax Amount: 0.52 Total Amount: 8.35
内容的提问来源于stack exchange,提问作者Jack

