如何避免使用引用类型的重复代码?

How can I avoid repetitive code using reference types?

我创建了一种方法,可以将地址行拆分为单独的不同类别,例如地区、城市、街道和房屋,它们由单个 class 表示。它遵循相对简单的结构。

Address useOther(string Casual, string Other)
    {
        Address
            address = new Address();

        List<string>
            split = new List<string>();
        char[]
            separator = { ',' };
        split.AddRange(
            Casual.Split(
                separator, 
                StringSplitOptions.RemoveEmptyEntries));
        split.AddRange(
            Other.Split(
                separator, 
                StringSplitOptions.RemoveEmptyEntries));

        // Initialize parameters
        split = RemoveBannedTokens(split);

        for (int i = 0; i < split.Count; i++)
        {
            (address.Region, split[i]) = findRegionByName(split[i]);

            if (address.Region != null)
            {
                split[i] = split[i].Replace(address.Region.Name, "");
                break;
            }
        }

        if (address.Region == null)
        {
            address.Region = new Region(region);
        }

        split = ClearSplit(split);

        // Finding region
        for (int i = 0; i < split.Count; i++)
        {
            (address.City, split[i]) = findSityByName(split[i]);

            if (address.City != null)
            {
                split[i] = split[i].Replace(address.City.Name, "");
                break;
            }

        }
        if (address.City == null)
        {
            for (int i = 0; i < split.Count; i++)
            {
                (address.City, split[i]) = findCityByName(split[i], false);

                if (address.City != null)
                {
                    split[i] = split[i].Replace(address.City.Name, "");
                    break;
                }

            }
        }
        if (address.City == null)
            return address;
        split = ClearSplit(split);

        // Finding city
        for (int i = 0; i < split.Count; i++)
        {
            (address.Street, split[i]) = findStreetByName(split[i], address.City);

            if (address.Street != null)
            {
                split[i] = split[i].Replace(address.Street.Name, "");
                break;
            }
        }
        if (address.Street == null && 
            (address.Region.Code.Replace("/", "") + 
             address.City.Code.Replace("/", "")).Length != 13)
            return address;
        split = ClearSplit(split);
        //Finding street

        int
            cityIndex = address.City.Index,
            streetIndex = address.Street.Index;

        for (int i = 0; i < split.Count; i++)
        {
            split[i] = split[i].Replace("б/н", "");
        }
        address.House = findNumbers(split.ToArray(), cityIndex, streetIndex);

        return address;
    }
    

如果你能看到重复是这样的

for (int i = 0; i < split.Count; i++)
{
    (address.Something, split[i]) = findSomethingByName(split[i]);

    if (address.Something != null)
    {
        //do this thing
        break;
    }

}
if (address.Something == null)
    //try other thing
split = ClearSplit(split);

我想避免这种重复并使用数组或 foreach 循环,但找不到一种明确的方法来制作具有引用值的数组或可以正确处理空值的 foreach 引用循环。我考虑过制作一个具有指定输入和输出的函数,但认为它只会损害阅读我的代码的能力,我正在努力改进这里。

让我试着证明你的问题和我的建议。

假设

  1. 您的 class 有几个相似的属性,都具有 String 的值。
  2. 您必须保留属性,因为其他框架可能需要它们。
  3. 您有重复代码来处理这些属性。

旧地址

public class OldAddress
{
    public string City { get; set; }
    public string Region { get; set; }
}

旧程序

public class Program
{

    static OldAddress oldAddress;

    public static string GetFromDataSource(string prop)
    {
        return null;//should return your real value
    }

    public static void AssignValueOldWay()
    {
        if (oldAddress.City != null)
        {
            oldAddress.City = GetFromDataSource("City");
        }
        if (oldAddress.Region != null)
        {
            oldAddress.Region = GetFromDataSource("Region");
        }
        // more...
    }
}

新地址

public class NewAddress
{
    public static string[] PropNames = { "City", "Region" };
    public NewAddress()
    {
        props = new Dictionary<string, string>();
        foreach (string prop in PropNames)
        {
            props.Add(prop, "");
        }
    }

    public string City
    {
        get
        {
            return GetProperty("City");
        }
        set
        {
            SetProperty("City", value);
        }
    }
    public string Region
    {
        get
        {
            return GetProperty("Region");
        }
        set
        {
            SetProperty("Region", value);
        }
    }

    private Dictionary<string, string> props;

    public void SetProperty(string prop, string value)
    {
        props[prop] = value;
    }

    public string GetProperty(string prop)
    {
        return props[prop];
    }

}

新计划

public class Program
{

    public static string GetFromDataSource(string prop)
    {
        return null;//should return your real value
    }

    static NewAddress newAddress;

    public static void AssignValueNewWay()
    {
        foreach (string prop in NewAddress.PropNames)
        {
            if (newAddress.GetProperty(prop) != null)
            {
                newAddress.SetProperty(prop, GetFromDataSource(prop));
            }
        }
    }
}

请注意,我的代码只是为了演示这个想法,并未针对运行时异常或不良样式(例如 public 字段或魔术字符串)进行优化。

经过一番思考和研究如何解决这个问题后,我决定听从 JAlex 的建议。目前我认为没有必要进行此更改,所以我暂时保留它。我要做的是尝试通过使用额外的空格和注释来使其更具可读性。截至目前,我的方法看起来像这样(在这里和那里进行了一些调整之后),我或多或少对它的可读性感到满意

Address useOther(string Casual, string Other)
{
    Address address = 
        new Address();
    //Initialize address

    List<string> split = 
        new List<string>();
    char[] separator = 
    { 
        ',' 
    };
    //Initialize split and separators

    split.AddRange(Casual.Split(separator));
    split.AddRange(Other .Split(separator));
    RemoveBannedTokens(ref split);
    //Fill split



    for (int i = 0; i < split.Count; i++)
    {
        (address.Region, split[i]) = 
            findRegionByName(split[i]);

        if (address.Region != null)
            break;
    }
    //Trying to find region

    if (address.Region == null)
        address.Region = new Region(region);
    //If Region is not found

    ClearSplit(ref split);
    //Get rid of empty strings



    for (int i = 0; i < split.Count; i++)
    {
        (address.City, split[i]) = 
            findSityByName(split[i]);

        if (address.City != null)
            break;
    }
    //Trying to find city

    if (address.City == null)
    {
        for (int i = 0; i < split.Count; i++)
        {
            (address.City, split[i]) = 
                findSityByName(split[i], false);

            if (address.City != null)
                break;
        }
    }
    //Trying to find city with additional params

    if (address.City == null)
        return address;
    //If City is not found

    ClearSplit(ref split);
    //Get rid of empty strings



    for (int i = 0; i < split.Count; i++)
    {
        (address.Street, split[i]) = 
            findStreetByName(split[i], address.City);

        if (address.Street != null)
            break;
    }
    //Trying to find street

    string code =
        address.Region.Code.Replace("/", "") +
        address.City.Code  .Replace("/", "");
    //Initialize code

    if (address.Street == null && code.Length != 13)
        return address;
    //If Street is not found and address is not complete

    ClearSplit(ref split);
    //Finding street

    address.House = findNumbers(split);
    //Trying to find house

    return address;
}

我有一些空闲时间,所以我决定重构你的代码,作为我自己的一次心理锻炼。

我仍然想 post 我的解决方案,以防您有兴趣查看它。最根本的转变是,我将我的想法从 imperative programming to functional programming 转变为更简洁的代码,对于习惯这种范式的人来说,代码可读性更高。

Address userOther(string Casual, string Other)
{
    var tokens = RemoveBannedTokens(Casual.Split(',', StringSplitOptions.RemoveEmptyEntries)
                                          .Concat(Other.Split(',', StringSplitOptions.RemoveEmptyEntries))
                                          .ToList());

    //Region
    var regionResult = tokens.Select(findRegionByName).FirstOrDefault(t => t.Item1 != null);
    regionResult = regionResult == default ? (region, string.Empty) : regionResult;
    tokens.Remove(regionResult.Item2);

    //City
    var cityResult = tokens.Select(findSityByName).FirstOrDefault(t => t.Item1 != null);
    cityResult = cityResult == default ? tokens.Select(t => findCityByName(t, false)).FirstOrDefault(t => t.Item1 != null) : cityResult;
    cityResult = cityResult == default ? (default(City), string.Empty) : cityResult;
    if (cityResult.Item1 == null)
    {
        return new Address(regionResult.Item1);
    }
    tokens.Remove(cityResult.Item2);

    //Street
    var streetResult = tokens.Select(t => findStreetByName(t, cityResult.Item1)).FirstOrDefault(t => t.Item1 != null);
    if (streetResult.Item1 == null &&
        (regionResult.Item1.Code.Replace("/", "") + cityResult.Item1.Code.Replace("/", "")).Length != 13)
    {
        return new Address(regionResult.Item1, cityResult.Item1);
    }
    tokens.Remove(streetResult.Item2);

    return new Address(regionResult.Item1,
                       cityResult.Item1,
                       streetResult.Item1,
                       findNumbers(tokens.Select(t => t.Replace("б/н", "")).ToArray(), cityResult.Item1.Index, streetResult.Item1.Index));
}

注意几点:

  • 由于你没有提供这个方法使用的一些方法,我无法测试这个方法的真实行为
  • 我假设从方法 findXXXByName 返回的元组的第二项与提供的值相同,并且没有费心用它更新 tokens[i]
  • 您的示例中没有提供变量 region,但我仍然决定保持原样
  • 我在返回实例时创建了 Address 个实例,这迫使我声明多个构造函数(您目前可能没有)
  • 我假设 ClearSplit() 只是从标记中删除空字符串

我希望您会发现这段代码对您的职业道路和个人学习很有价值。