【问题标题】:Handling Functions with Same Signature but Different Parameter Meanings处理签名相同但参数含义不同的函数
【发布时间】:2018-10-30 20:24:56
【问题描述】:

我一直在阅读 Robert C. Martin 的 Clean Code。他在其中指出,函数应根据需要使用最少数量的参数,并且函数名称应遵循项目范围的命名约定,同时避免无缘无故的上下文。

例如,如果我为这个问题写了一个简单的类,Car:

public class Car
    {
        internal string make { get; set; }
        internal string model { get; set; }

        public Car(string make, string model)
        {
            this.make = make;
            this.model = model;
        }
    }

还有一个班级Cars

public class Cars
{
    internal List<Car> cars { get; set; }

    public Cars(List<Car> cars)
    {
        this.cars = cars;
    }

    public Car GetCar(string make)
    {
        foreach (Car car in cars)
        {
            if (car.make == make) return car;
        }
        return null;
    }

    public Car GetCar(string model)
    {
        foreach (Car car in cars)
        {
            if (car.model == model) return car;
        }
        return null;
    }

}

显然这不会编译,因为名为 GetCar 的函数具有相同的函数签名。

通常我可以通过重命名函数GetCarByMakeGetCarByModel 或类似的名称来解决这个问题,但文本似乎表明这不是最佳实践。其他时候,我可能会添加一个额外的未使用参数来区分它们。

我的下一个想法是为Car 中的每个属性创建一个类;有一个包含字符串属性的MakeModel 类。但是,这似乎引入了不必要的开销。

那我很好奇,我怎样才能实现/命名多个函数,它们通常做类似的事情,具有相同的参数数据类型,并输出相同的数据类型,而没有不同的函数名称?另外,我对这个问题或类似主题的额外函数重载或多态性不感兴趣;我想知道关于课程的答案,因为它们目前没有重大变化。我目前能看到的唯一答案是为每个属性创建类。

如果需要任何澄清或更改,请告诉我。

编辑: 正如一些附加信息一样,这纯粹是一项学术练习。从专业上讲,我会简单地创建具有不同名称的方法并完成它。如 cmets 中所述,它简单、可维护且有效。

【问题讨论】:

  • 对于它的价值,如果您不愿意添加接口等 - 我会给方法不同的名称。它很好,简单,易于阅读和理解,并且可以编译。
  • @mjwills 好点。我已经改写了我的问题以删除基于意见的方面并使其成为一个简单的编程问题。感谢您的提醒!在旁注中,我认为使用不同的方法名称是最好的方法。但是,我想看看是否有其他方法。
  • 可以有不同的方法,所有方法都有效且干净。有些会使用方法重载,有些可以使用不同的方法,有些可以使用带有动态内联 Linq 查询的默认值参数。如果可以使用适当的文档和命名约定来维护代码,那么所有这些都可以是干净的。这是不可衡量的,除非有人从全球收集源代码来分析模式和设计原则。
  • @RyanSchlueter 我知道我可以重命名这些方法并且没问题(正如我的问题所证明的那样)。我的问题是是否可以在不使用虚拟参数的情况下保持相同的方法名称,并避免为我的属性使用类。

标签: c# architecture software-design


【解决方案1】:

通过显式接口实现,可以在同一个类中拥有多个具有相同“名称”和签名的方法:

public interface IFindByMake
{
     Car GetCar(string make);
}

public interface IFindByModel
{
     Car GetCar(string model);
}

public class Cars : IFindByMake, IFindByModel
{
    internal List<Car> cars { get; set; }

    public Cars(List<Car> cars)
    {
        this.cars = cars;
    }

    Car IFindByMake.GetCar(string make)
    {
        foreach (Car car in cars)
        {
            if (car.make == make) return car;
        }
        return null;
    }

    Car IFindByModel.GetCar(string model)
    {
        foreach (Car car in cars)
        {
            if (car.model == model) return car;
        }
        return null;
    }
}

上面的代码可以编译;然而,你会注意到,如果你声明了一个 Cars 类型的变量,就没有方法 GetCar 可用。如果您强制转换为两种接口类型之一或将变量声明为两种类型之一,则只能调用该方法。

var cars = new Cars();
((IFindByMake)cars).GetCar(“Ford”);

或者

IFindByModel cars = new Cars();
cars.GetCar(“Mustang”);

否则编译器怎么会知道调用哪个方法?

【讨论】:

  • 很好的回答戴夫!我想了一晚上,得出的结论是,与此类似的方法将是唯一可行的方法。但是,使用此方法显然会遇到问题,即您只能将该方法用于您最初将 Cars 变量转换为的任何接口。您将无法使用其他方法。但是,就像您说的那样,需要某种方法来区分函数,以便编译器可以区分。我知道的唯一其他方法是在功能级别区分它们,这与重命名它们是同义的。
【解决方案2】:

你可以使用下面提到的 linq

public Car GetCar(string make, string model)
{

    return cars.Where(w=>  (string.IsNullOrEmpty(make) || w.make == make) &&(string.IsNullOrEmpty(model) || w.model == model)).Select(s=>s).FirstOrDefault();
}

【讨论】:

  • 假设我想要一个型号为null 的汽车列表,而我并不关心品牌。这个查询能做到吗?
  • 感谢 Hitesh!我选择将 Dave 的答案标记为最佳答案,因为它最好地回答了我提出的问题,但我确实认为您的方法是更实用的方法。使用接口,就像我在另一条评论中提到的那样,删除了一些需要的功能。
  • @mjwills:这是一个示例查询,表明这是一种使用方式,根据要求修改逻辑,信息存储方式。如果需要任何其他参数,请添加它们以获得所需的结果。这取决于如何处理“null”,对于上述查询,“当参数中没有传递任何内容时,获取所有记录”是假定要求。
  • @Jack:谢谢。我不介意将 Dave 的答案标记为最佳答案。我使用混合方法,基于拥有良好组织代码的意义。我更喜欢使用接口来区分两种或多种实体的行为。如果我的车辆需要不同品牌或型号的搜索参数实现,我想我会使用接口。对于使用属性进行过滤,我可以使用一个大的动态 linq,或者可以使用具有不同方法名称的私有函数,以便由具有两个参数的公共方法调用。
猜你喜欢
  • 2020-04-19
  • 1970-01-01
  • 1970-01-01
  • 2011-01-24
  • 1970-01-01
  • 1970-01-01
  • 2017-07-20
  • 2010-09-29
  • 1970-01-01
相关资源
最近更新 更多