PDFから読み取り可能なデータを解析しようとしていますが、次のようなコードを書いてしまいます。
if (IsDob(line))
{
dob = ParseDob(line);
}
else if (IsGender(line))
{
gender = ParseGender(line);
}
...
...
...
else if (IsRefNumber(line))
{
refNumber = ParseRefNumber(line);
}
else
{
unknownLines.Add(line);
}
次に、このすべてのデータを使用して、関連するオブジェクトを構築します。すべての個人データを使用している顧客。
解析することがたくさんあるとき、これは一種の醜いものになる傾向があります。
それらをTryParsePersonalInfo(line)、TryParseHistory(line)などの関数に分割しました。しかし、どこにでも無限のelse ifがあるので、問題が動いているように感じます。
これは、あなたが提供した情報を踏まえて、私が最初に行うことです。
次のようなインターフェースを作成します。
interface LineProcessor<E> {
boolean Process(Record record, Line line);
}
今のところRecordは適切なバッグだとしましょう。これにこだわらないでください。デモンストレーションのために単純にしています。
class Record {
public Date dob { get; set; }
public String gender { get; set; }
public String refNumber { get; set; }
// ...
}
構文がC#に対して正しくない場合は、申し訳ありません。私が何をしているのかわからない場合は、明確にします。
次に、LineParserのインスタンスのリストを作成します。次に、ループを記述します(python/pseudocode):
for line in pdf:
obj = process(record, line)
def process(record, line):
for handler in processors:
if handler.process(record, line): return
else:
unknown.add(line)
これらのいずれかの実装は次のようになります。
class DobProcessor implements LineProcessor {
boolean process(Record record, Line line) {
if (IsDob(line)) {
record.dob = ParseDob(line);
return true;
} else {
return false;
}
}
Date ParseDob(Line line) {
//...
}
boolean IsDob(Line line) {
//...
}
}
これにより、コードが管理しやすくなります。大きなifステートメントの代わりに、それぞれがケースに固有である多くのクラスがあります。これにより、コードが整理されるだけでなく、他のケースの周囲のコードに触れることなく、ケースを追加または削除できるようになります。
もう1つは、プロセスインターフェイスが単一のメソッドになっているため、これを実際には関数ポインター/ラムダのようなものと考えることができるため、必要に応じてインターフェイスを省略できます。
...拡張可能なオブジェクトモデルに含まれる注入可能なクラスにカプセル化されたidemopotent解析関数を呼び出す
ここでは、いくつかのプログラミングの概念を紹介しますので、長い答えに我慢してください。慣れるまで、これは複雑すぎるように見えるかもしれません。しかし、これらのパターンはすべて非常に一般的であり、商用ソフトウェアでは非常に便利です。
まず、結果をまとめて保存する場所が必要です。おそらくDTOクラス。この例のようになります。 ToString()
オーバーライドを追加したので、クラス名だけでなく、デバッグペインに内容を表示できます。
_public enum Gender
{
Male, Female
}
public class DocumentMetadata
{
public DateTime DateOfBirth { get; set; }
public Gender Gender { get; set; }
public string RefNum { get; set; }
public override string ToString()
{
return string.Format("DocumentMetadata: DOB={0:yyyy-MM-dd}, Gender={1}, RefNum={2}", DateOfBirth, Gender, RefNum);
}
}
_
これでDTOが作成されたので、行を解析する方法について考えることができます。理想的には、ユニットテストを簡単に実行できる一連のべき等関数が必要です。そして、それらを繰り返し処理するために、それらが何らかの形で類似している場合に役立ちます。したがって、デリゲートを定義します。
_public delegate bool Parser(string line, DocumentMetadata dto);
_
したがって、次のようなパーサーメソッドを記述できます。
_protected bool ParseDateOfBirth(string line, DocumentMetadata dto)
{
if (!line.StartsWith("DOB:")) return false;
dto.DateOfBirth = DateTime.Parse(line.Substring(4));
return true;
}
_
任意の数のパーサーメソッドを記述でき、ブール値を返し、文字列とDTOオブジェクトを引数として受け入れる限り、これらはすべてデリゲートに一致するため、次のようにすべてリストに入れることができます。
_List<Parser> parsers = new List<Parser>
{
ParseDateOfBirth,
ParseGender,
ParseRefNum
};
_
この機能は、パーサークラスを作成するときに使用します。
ここで、もう少し心配することがあります。
したがって、最初にインターフェースを定義します。
_public interface IDocumentParser
{
DocumentMetadata Parse(IEnumerable<string> input);
}
_
抽象基本パーサー:
_public abstract class BaseParser : IDocumentParser
{
protected abstract List<Parser> GetParsers();
public virtual DocumentMetadata Parse(IEnumerable<string> input)
{
var parsers = this.GetParsers();
var instance = new DocumentMetadata();
foreach (var line in input)
{
foreach (var parser in parsers)
{
parser(line, instance); //This is the line that does it all!!!
}
}
return instance;
}
}
_
または、Parse関数をもう少し賢くしたい場合(および正常に解析された行を数える場合):
_ public virtual DocumentMetadata Parse(IEnumerable<string> input)
{
var parsers = this.GetParsers();
var instance = new DocumentMetadata();
var successCount = input.Sum( line => parsers.Count( parser => parser(line, instance) ));
Console.WriteLine("{0} lines successfully parsed.", successCount);
return instance;
}
_
LINQソリューションはより「巧妙」ですが、ネストされたループは意図をより明確に伝えます。ここで判断の呼び出し。成功した行を数えることができ、その情報を使用してドキュメントを検証できるため、LINQバージョンが好きです。
これで、ドキュメントを解析するための基本的なフレームワークが完成しました。必要なのは、GetParsers
を実装して、機能するメソッドのリストを返すことだけです。
_public class DocumentParser : BaseParser
{
protected override List<Parser> GetParsers()
{
return new List<Parser>
{
ParseDateOfBirth,
ParseGender,
ParseRefNum
};
}
private bool ParseDateOfBirth(string line, DocumentMetadata dto)
{
///Implementation
}
private bool ParseGender(string line, DocumentMetadata dto)
{
///Implementation
}
private bool ParseRefNum(string line, DocumentMetadata dto)
{
///Implementation
}
}
_
最終的な実装では、ドキュメント固有のロジックのみが保持されていることに注意してください。そして、GetParsers()
を介してデリゲートを提供するだけです。すべての一般的なロジックは、再利用できる基本クラスにあります。
これで、数行のコードでドキュメントを解析できます。
_var parser = new DocumentParser();
var doc = parse.Parse(input);
_
しかし、これを挿入したいので、適切に書きましょう:
_public class Application
{
protected readonly IDocumentParser _parser; // injected
public Application(IDocumentParser parser)
{
_parser = parser;
}
public void Run()
{
var input = new string[]
{
"DOB:1/1/2018",
"Sex:Male",
"RefNum:1234"
};
var result = _parser.Parse(input);
Console.WriteLine(result);
}
}
public class Program
{
public static void Main()
{
var application = new Application(new DocumentParser());
application.Run();
}
}
_
出力:
_3 lines successfully parsed.
DocumentMetadata: DOB=2018-01-01, Gender=Male, RefNum=1234
_
これで、次のすべてが揃いました。
よりC#風の解決策は、デリゲートを利用することです。
delegate bool TryParseHandler(string line);
private readonly TryParseHandler[] _handlers = new[]
{
TryParseDob,
TryParseGender,
TryParseRefNumber,
//...
}
bool TryParseDob(string line)
{
if(!IsDob(line)) return false;
dob = ParseDob(line);
return true;
}
//etc
void ProcessLine(string line)
{
foreach(var handler in _handlers)
{
if(handler(line)) return;
}
}
正解は、「is」ソリューションを完全に捨てることです。 「ライン」はどのようなものですか?定期的ですか?キーワードは含まれていますか?たとえば、dob: 1/1/1990
、gender: female
、ref: 123456
のように見えますか?もしそうなら、あなたはそれらのキーワードを引き出して、それをあなたの検索に使いたいです:
private readonly IReadOnlyDictionary<string, Action<string>> _setValueLookup = new Dictionary<string, Action<string>>
{
["dob"] = s => dob = DateTime.Parse(s),
["gender"] = s => gender = ParseGender(s),
["ref"] = s => refString = s,
};
void ProcessLine(string line)
{
var pair = line.Split(new []{':'}, 2);
if(pair.Length < 2)
{
unknownLines.Add(line);
return;
}
var key = pair[0];
var value = pair[1];
if(!_setValueLookup.TryGetValue(key, out Action<string> callback))
{
unknownLines.Add(line);
return;
}
callback(value);
}