2016-12-13 5 views
1

저는 C#에서 여러 가지 유틸리티, 재사용 가능한 함수를 계속 작성합니다. 많은 함수는 out 또는 ref 매개 변수를 사용하는 여러 값을 반환합니다. 많은 기능에는 추가 정보가있어 일부 발신자 (일부 발신자는 사용할 수 없음)에게 유용 할 수 있습니다.함수에서 여러 개의 선택적 값을 반환하는 디자인 또는 코드 패턴

예를 들어 CSV 파일을 읽는 기능에는 no와 같은 추가 정보가있을 수 있습니다. 빈 줄, 아니. 중복 값이있는 행 및 기타 통계를 표시합니다.

추가 정보는 등의 경고 메시지를 포함 할 수

아니 모든 호출자는이 정보에 관심이있을 것이다, 그래서 나는 할 모든 이들과 같은 아웃, 심판 또는 튜플을 포함하지 않을 호출자가 이러한 모든 예상 변수를 선언해야합니다.

난 그냥 발신자가 선택하거나 관심 옵션 추가 정보의 일부를 검색 할 수 있도록, 호출자가 사용할 수있는 추가 정보를 만들 수있는 방법이 있는지 파악하고 싶었다. 예를 들어

Func 1은 Func B를 호출 할 수 있습니다. 호출 한 후 모든 표준 반환 값을 가져옵니다. 또한 FuncB를 다시 실행하지 않고도 FuncB.GetAdditionalInfo (infoType)와 같은 것을 호출 할 수 있습니까?

중개자 역할을하는 클래스를 사용하여 모든 옵션 값을 저장 한 다음 요청시 발신자에게 반환 할 수도 있습니다. 하지만 필자는 모든 유틸리티 기능에서 사용할 수있을만큼 일반화되기를 원합니다.

호출기가 필요에 따라 액세스 할 수있는 일종의 전역 변수에 이러한 모든 것을 저장하는 Func B가 한 가지 가능성이 있습니다. 그러나 유틸리티 클래스에 이러한 재개 가능한 함수가 여러 개 있으면 각 함수의 추가 정보를 위해 많은 공용 변수가 필요합니다!

현재 .NET 4.5 버전입니다. 이것을위한 디자인 패턴이 있습니까? F #에 좋은 해결책이 있는지 알고 싶습니다.

또한이 작업을 수행하기 위해 너무 많은 오버로드 된 버전을 피하려고합니다. 감사합니다. .

+1

일부 메타 데이터 개념에 데이터를 저장할 수 있습니다. 키 - 값 사전 일뿐입니다. – Michael

+0

그래, 가능하다면 매번 호출자의 값을 캐스팅하지 않아도되고 싶다. – AllSolutions

+0

그리고 관심이없는 경우이 메타 데이터를 호출자에게 옵션으로 지정하려면 어떻게해야합니까? – AllSolutions

답변

2

나는 이상적인 구현 방법을 제시하지 않겠지 만, 나에게 맞는 것은 여기있다. 두 개의 서로 다른 데이터 구조를 디자인하십시오. 하나는 함수가 받아 들일 수있는 옵션이고 다른 하나는 함수가 반환하는 옵션입니다. 예를 들어 :

위의 예 CountBlankLines에서
public class Helper 
{ 

    // General cover-it-all implementation that accepts an option object 
    // and analyzes based on the flags that are set in it 
    public static CSVStatistics AnalyzeCSV(string file, CSVAnalysisOptions options) 
    { 
     // define what we are analysing by reading it from the 
     // from the options object and do your magic here 
    } 

    // Specific implementation that counts only blank lines 
    public static long CountBlankLines(string file) 
    { 
     var analysisResult = AnalyseCSV(file, new CSVAnalysisOptions 
     { 
      IsCountingBlanks = true 
     }); 
     //I'm not doing a null check here, because I'm settings the 
     //flag to True and therefore I expect there to be a value 
     return analysisResult.BlanksCount.Value; 
    } 
} 
// Analysis options structure 
public struct CSVAnalysisOptions 
{ 
    public bool IsCountingBlanks { get; set; } 
    public bool IsCountingDuplicates { get; set; } 
    public bool IsCountingOther { get; set; } 
} 

// Analysis results structure 
public struct CSVStatistics 
{ 
    public long TotalLineCount { get; set; } 
    public long? BlanksCount { get; set; } 
    public long? DuplicatesCount { get; set; } 

} 

AnalyzeCSV 실제로 계산을 할 것입니다 방법 동안, 전화를 간단하게 "설탕"으로 만 빈 줄과 행위를 카운트 특정 구현이다. 또한 CSStatistics 구조체에 nullable long이 들어있는 구조에 주목하십시오. 이렇게하면 값이 null인지 여부를 확인할 수 있으므로 0을 출력하는 대신 실제로 분석되지 않았 음을 알 수 있습니다 (가능한 값).

구조체도 비트 플래그로 바꿀 수 있습니다.이 부분에 대한 내용은 https://www.dotnetperls.com/enum-flags입니다.

2

내가하려는 것은 매우 한 번에 많은 작업을 수행 할 수있는 두툼한 API를 구축하는 것입니다. 일반적으로 우리는 chunky API를 좋아하지 않습니다. API의 옵션 간 상호 작용에 부작용이나 특이한 단점이있는 경우 특히 복잡해질 수 있기 때문입니다.

정직하게 말하면, 이것을 수행하는 가장 좋은 방법은 각 호출이 한 가지를 수행하는 올바른 API를 만드는 것입니다.

이렇게하면 코드가 쉽게 인수 분해 및 단위 테스트가 끝납니다.

적당량의 chunkiness가 원인이 아니라는 것은 아닙니다.하지만 논리적이어야합니다.

예를 들어 PNG 또는 JPG와 같은 이미지 파일을 해독하여 이미지 너비, 높이, 해상도 및 색상 유형이 앞에 있어야합니다. 한 번에 모든 사람들을 붙잡는 것이 합리적입니다. 메타 데이터 정보 나 색상 프로파일을 즉시 찾아야합니까? 아마도 그렇지 않습니다.

그래서 모든 기본 이미지 정보를 반환하고 집계 한 다음 나머지를 얻기 위해 별도의 호출을 수행하는 것이 좋습니다.

"그러나 성능!" 당신은 "성능은 어떨까요?"라고 말합니다.

심플. 당신은 그것을 측정하고 무엇이 떨어지는지를 봅니다. 몇 년 전에 PNG 디코더를 썼고 청크를 순차적으로 읽는 libpng와 달리 파일의 모든 청크가 어디에 있는지를 매핑 한 데이터베이스를 만든 다음 해당 데이터베이스를 참조하여 주어진 청크를 찾습니다 . 놀랍게도 이는 성능에 큰 영향을 미치지 않으며 소비 코드를 읽고 유지하기가 훨씬 쉬워졌습니다.

여러 번 호출하게하고 성능 문제가있는 경우 문제를 해결하는 방법을 찾아야합니다. 일반적으로 정보를 가져 오는 개체에 대한 전용 캐시 또는 세션을 통해이 작업을 수행합니다.

당신이 말하는 것은 읽기 쉽지 않고 유지하기가 쉽지 않을 것입니다.

+0

각 유틸리티 함수는 전체 많은 것. 그것은 의도 한 일을하고, 그 과정에서, 나는 단지 호출자에게 전달하기를 원하는 몇 가지 메타 데이터를 선택합니다. 그게 다야! – AllSolutions

+0

내가 말하고자하는 것은 호출 수신자 함수가 실행 된 후에 호출 수신자 함수의 일부 로컬 변수에 액세스하는 것이다. 이것은 언어에 의해 직접 지원되지 않는다. 따라서 out, ref, Tuple, Dictionary 등을 사용하는 것 이외의 방법이 있는지 찾아 내려고 시도하십시오. – AllSolutions

+0

메타 데이터는 별도로 액세스해야합니다. 호출 코드의 흐름에 필요한 경우 메타 데이터가 아닙니다. 중요한 상태 인 경우 해당 객체를 적절한 객체로 묶어 반환하십시오. 중요한 국가가 아닌 경우 별도의 접근자를 만드십시오. – plinth

0

다음은 사용 사례에 적합한 F # 패턴입니다. 이것은 Argu 라이브러리가 명령 줄 인수에 사용하는 것과 거의 같은 패턴입니다. 함수에서 반환 할 수있는 가능한 모든 "플래그"를 포함하는 discriminated union을 선언합니다 (일부는 "인용 부호"로 묶을 수 있습니다). 단순한 부울 이상이되어야 함) 함수가 그 값의 목록을 반환 할 수 있습니다. 수십 가지가 있다면 그 집합은 가치가있을 수 있습니다 (목록을 선형으로 검색해야하기 때문에). 그러나 7 개 또는 8 개 이상의 플래그를 반환하지 않는다면 집합의 추가 복잡성은 그만한 가치가 있고 목록을 사용할 수도 있습니다.

비즈니스 로직이 갈 것 위치를 더미 기능이 패턴을 사용하는 방법을 설명 일부 F # 코드 :

type Notifications 
    | InputWasEmpty 
    | OutputWasEmpty 
    | NumberOfBlankLinesInOutput of int 
    | NumberOfDuplicateLinesInOutput of int 
    | NumberOfIgnoredErrors of int 
    // Whatever else... 

type ResultWithNotifications<'a> = 'a * Notifications list 
// The syntax "TypeA * TypeB" is F# syntax for Tuple<TypeA,TypeB> 
// And the 'a is F# syntax for a generic type 

type outputRecord = // ... insert your own data type here 

// Returns the filename of the output file, plus various notifications 
// that the caller can take action on if they want to 
let doSomeProcessing data : ResultWithNotifications<outputRecord list> 
    let mutable notifications = [] 
    let outputFileName = getSafeOutputFilename() 
    if List.isEmpty data then 
     notifications <- InputWasEmpty :: notifications 
    let output = data |> List.choose (fun record -> 
     try 
      let result = record |> createOutputRecordFromInputRecord 
      Some result 
     except e 
      eprintfn "Got exception processing %A: %s" record (e.ToString()) 
      None 
    ) 
    if List.isEmpty output then 
     notifications <- OutputWasEmpty :: notifications 
    if List.length output < List.length data then 
     let skippedRecords = List.length data - List.length output 
     notifications <- (NumberOfIgnoredErrors skippedRecords) :: notifications 
    // And so on. Eventually... 
    output |> writeOutputToFilename outputFileName 
    outputFileName, notifications // Function result 

는 잘만 F # 코드는 설명없이 이해하지만, 무엇이든이 있는지 명확하지 않은 경우 위의 내용을 알려 주시면 설명 드리겠습니다.