문제

I have several areas in a program where I am doing the following check on the same two booleans, but each spot has different text being written to a file via Stream Writer based on the value of the booleans.

Example

if (bool1 && bool2)
{
  StreamWriter.WriteLine("Stuff to the file");
  StreamWriter.WriteLine("Stuff 2 to the file");
  StreamWriter.WriteLine("Stuff 3 to the file");
}
else if (bool1) 
{
  StreamWriter.WriteLine("Stuff 4 to the file");
  StreamWriter.WriteLine("Stuff 5 to the file");
  StreamWriter.WriteLine("Stuff 6 to the file");
} 
else if (bool2) 
{
  StreamWriter.WriteLine("Stuff 7 to the file");
  StreamWriter.WriteLine("Stuff 8 to the file");
  StreamWriter.WriteLine("Stuff 9 to the file");
}

The thought process is, if both Booleans are true, write certain text to the file. Else if only bool1 is true, then write this bool1 specific text. Else if only bool2 is true, then write this bool2 specific text.

There are at least 3 places in my program where I'm doing these comparisons, with the WriteLine text based on the Boolean values being different in each place.

Is there a better way to do this kind of comparison logic? Such that I wouldn't need to be checking the same booleans over and over to write the various texts to the file.

I've considered writing a method that takes in 3 values representing what should be written to the file based on the Boolean comparisons. I am struggling however to understand how I would pass WriteLine calls as parameters to a method.

도움이 되었습니까?

해결책

Another way to solve it using the Design Patterns Strategy and Factory Method:

An interface defines the strategy, in this case, it say that the strategy is write something, without details about what.

interface IStrategy
{
    void Write();
}

For each variation of the strategy you implement the IStrategy:

class StrategyA : IStrategy
{
    //....
}
class StrategyB : IStrategy
{
    //....
}
class StrategyC : IStrategy
{
    //....
}

A factory method help you change the behavior of your application in runtime defining a strategy from parameters:

public static IStrategy Create(string type)
    {
        switch (type)
        {
            case "A":
                return new StrategyA();
            case "B":
                return new StrategyB();
            case "C":
                return new StrategyC();
            default:
                throw new NotImplementedException();
        }
    }

Usage is simplified to:

public void Write()
{
    using (var sw = new StreamWriter())
    {
        var strategy = StrategyFactory.Create("A");

        strategy.Write(sw);
    }
}

Notice you can pass the value "A" dynamically, also getting it from database:

And so the result is:

interface IStrategy
{
    void Write(StreamWriter streamWriter);
}

class StrategyA : IStrategy
{
    public void Write(StreamWriter streamWriter)
    {
        streamWriter.WriteLine("Stuff to the file");
        streamWriter.WriteLine("Stuff 2 to the file");
        streamWriter.WriteLine("Stuff 3 to the file");
    }
}

class StrategyB : IStrategy
{
    public void Write(StreamWriter streamWriter)
    {
        streamWriter.WriteLine("Stuff 4 to the file");
        streamWriter.WriteLine("Stuff 5 to the file");
        streamWriter.WriteLine("Stuff 6 to the file");
    }
}

class StrategyC : IStrategy
{
    public void Write(StreamWriter streamWriter)
    {
        streamWriter.WriteLine("Stuff 7 to the file");
        streamWriter.WriteLine("Stuff 8 to the file");
        streamWriter.WriteLine("Stuff 9 to the file");
    }
}

class StrategyFactory
{
    public static IStrategy Create(string type)
    {
        switch (type)
        {
            case "A":
                return new StrategyA();
            case "B":
                return new StrategyB();
            case "C":
                return new StrategyC();
            default:
                throw new NotImplementedException();
        }
    }
}

class Program
{
    public void Write()
    {
        using (var sw = new StreamWriter())
        {
            var strategy = StrategyFactory.Create("A");

            strategy.Write(sw);
        }
    }
}

This solution depends on the size of your system complexity. Otherwise, it may not be worth it. But still, it is a useful alternative.

다른 팁

Use a procedure to do the branching, either:

WriteLines(IEnumerable<string> linesBoth, IEnumerable<string> linesOnlyA, IEnumerable<string> linesOnlyB)
{
    // ...
}

or:

WithStream(Action<StreamWriter> onBoth, Action<StreamWriter> onA, Action<StreamWriter> onB)
{
     // ...
}

Since there's clear coupling among the classes within your project, for this problem, you could and probably should utilise the template method pattern. It introduces the same coupling but makes sure it's present only once. And the implementation can then focus only on the parts which are different, rather than also taking care of the execution of methods based on conditions.

You create a class acting as a template, indicating how and in which order methods are to be called:

public abstract class MyStreamWriterTemplate
{

    public final write(...)
    {
        if (bool1 && bool2)
        {
            this.writeWhenBothTrue();
        }
        else if (bool1)
        {
            this.writeWhenFirstTrue();
        }
        else if (bool2)
        {
            this.writeWhenSecondTrue();
        }
    }

    protected abstract writeWhenBothTrue();

    protected abstract writeWhenFirstTrue();

    protected abstract writeWhenSecondTrue();
}

and a concrete implementation then inherits this template class and provides custom functionality:

public class WriterA extends MyStreamWriterTemplate
{
    private final StreamWriter streamWriter;

    public WriterA(StreamWriter streamWriter)
    {
        super();

        this.streamWriter = streamWriter;
    }

    protected writeWhenBothTrue()
    {
        streamWriter.WriteLine("Both true from A");
    }

    protected writeWhenFirstTrue()
    {
        streamWriter.WriteLine("First true from A");
    }

    protected writeWhenSecondTrue()
    {
        streamWriter.WriteLine("Second true from A");
    }
}

If you later find out that for certain cases, you only really need to write e.g. when only the second attribute is true, you could change the template to not have abstract methods but to rather provide default implementation of doing nothing.

That way you won't force the actual implementing classes to implement the abstract methods just to provide no behaviour.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 softwareengineering.stackexchange
scroll top